diff --git a/src/main/api/studio-api.yaml b/src/main/api/studio-api.yaml
index ec85208c2b..c9eb99be9f 100644
--- a/src/main/api/studio-api.yaml
+++ b/src/main/api/studio-api.yaml
@@ -2360,95 +2360,6 @@ paths:
'500':
$ref: '#/components/responses/InternalServerError'
- /api/1/services/api/1/content/get-content-type.json:
- get:
- tags:
- - content
- summary: Get content type configuration.
- description: "Required role: N/A"
- operationId: getContentType
- parameters:
- - name: site_id
- in: query
- description: Project/Site ID to use
- required: true
- schema:
- type: string
- - name: type
- in: query
- schema:
- type: string
- description: Content type
- required: true
- example: /page/category-landing
- responses:
- '200':
- description: OK
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ContentType'
- example:
- name: "/page/category-landing"
- label: "Category Landing"
- form: "/page/category-landing"
- formPath: "simple"
- type: "page"
- contentAsFolder: true
- useRoundedFolder: false
- modelInstancePath: "NOT-USED-BY-SIMPLE-FORM-ENGINE"
- allowedRoles: [ ]
- lastUpdated: "2023-11-10T18:00:05.882948Z"
- copyDependencyPattern: [ ]
- imageThumbnail: "page-category-landing.png"
- noThumbnail: false
- pathIncludes: [ "^/site/website/(?!articles/)(.*)" ]
- pathExcludes: [ ]
- nodeRef: "null"
- quickCreate: false
- quickCreatePath: ""
- deleteDependencyPattern: [ ]
- previewable: true
- '400':
- $ref: '#/components/responses/api1BadRequest'
- '401':
- $ref: '#/components/responses/Unauthorized'
-
- /api/1/services/api/1/content/get-content-types.json:
- get:
- tags:
- - content
- summary: Get content types allowed for given path.
- description: "Required role: N/A"
- operationId: getContentTypes
- parameters:
- - name: site
- in: query
- description: Project/Site ID to use
- required: true
- schema:
- type: string
- - name: path
- in: query
- schema:
- type: string
- description: Path to get content types for
- required: true
- example: /site/website/
- responses:
- '200':
- description: OK
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/ContentType'
- '400':
- $ref: '#/components/responses/api1BadRequest'
- '401':
- $ref: '#/components/responses/Unauthorized'
-
/api/1/services/api/1/content/get-item-orders.json:
get:
tags:
@@ -3887,16 +3798,16 @@ paths:
# '403':
# $ref: '#/components/responses/Forbidden'
- /api/2/configuration/content-type/usage:
+ /api/2/configuration/content_types/{siteId}/usage:
get:
tags:
- - configuration
+ - contentTypes
summary: Get all usage of a given content-type
description: 'Required permission "content_read"'
operationId: getContentTypeUsage
parameters:
- name: siteId
- in: query
+ in: path
description: site ID
required: true
schema:
@@ -3941,16 +3852,16 @@ paths:
'403':
$ref: '#/components/responses/Forbidden'
- /api/2/configuration/content-type/preview_image:
+ /api/2/configuration/content_types/{siteId}/preview_image:
get:
tags:
- - configuration
+ - contentTypes
summary: Get preview image of a given content type
description: 'Required permission "content_read"'
operationId: getContentTypePreviewImage
parameters:
- name: siteId
- in: query
+ in: path
description: site ID
required: true
schema:
@@ -3988,69 +3899,115 @@ paths:
'500':
$ref: '#/components/responses/InternalServerError'
- /api/2/configuration/content-type/form_controller:
+ /api/2/configuration/content_types/{siteId}/allowed_types:
get:
tags:
- - configuration
- summary: Get the form controller (if it exists) script of a given content type
+ - contentTypes
+ summary: Get allowed content types for a given site at a given path
description: 'Required permission "content_read"'
- operationId: getContentTypeFormController
+ operationId: getAllowedContentTypes
parameters:
- name: siteId
- in: query
+ in: path
description: site ID
required: true
schema:
- type: string
- - name: contentTypeId
+ type: string
+ - name: path
in: query
required: true
schema:
- type: string
+ type: string
responses:
'200':
- description: 'OK'
+ description: OK
content:
- '*/*':
- schema:
- type: string
- format: binary
- description: The content of the form controller file
- headers:
- Content-Type:
- description: The MIME type of the form controller file
+ application/json:
schema:
- type: string
- Content-Length:
- description: The size in bytes of the form controller file
+ type: object
+ properties:
+ response:
+ $ref: '#/components/schemas/ApiResponse'
+ allowedTypes:
+ type: array
+ items:
+ type: string
+ example:
+ - "page/article"
+ - "page/news"
+ '400':
+ $ref: '#/components/responses/BadRequest'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+ '500':
+ $ref: '#/components/responses/InternalServerError'
+
+ /api/2/configuration/content_types/{siteId}:
+ get:
+ tags:
+ - contentTypes
+ summary: Get content type configuration file for a given content type. It gets all content types if no contentTypeId is provided
+ description: 'Required permission "content_read"'
+ operationId: getContentTypeConfiguration
+ parameters:
+ - name: siteId
+ in: path
+ description: site ID
+ required: true
+ schema:
+ type: string
+ - name: contentTypeId
+ in: query
+ schema:
+ type: string
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
schema:
- type: integer
- format: int64
+ type: object
+ properties:
+ response:
+ $ref: '#/components/schemas/ApiResponse'
+ contentTypes:
+ type: array
+ items:
+ $ref: '#/components/schemas/ContentType'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
- /api/2/configuration/content-type/delete:
- post:
+ delete:
tags:
- - configuration
+ - contentTypes
summary: Delete files related to a given content-type
description: 'Required permission "write_configuration"'
operationId: deleteContentType
+ parameters:
+ - name: siteId
+ in: path
+ description: site ID
+ required: true
+ schema:
+ type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
- siteId:
- type: string
- description: The id of the site
contentType:
type: string
description: The content-type to delete
@@ -4058,7 +4015,6 @@ paths:
type: boolean
description: Indicates if all dependencies of the content-type should be deleted (defaults to false)
required:
- - siteId
- contentType
responses:
'200':
@@ -4077,6 +4033,52 @@ paths:
'403':
$ref: '#/components/responses/Forbidden'
+ /api/2/configuration/content_types/{siteId}/form_controller:
+ get:
+ tags:
+ - contentTypes
+ summary: Get the form controller (if it exists) script of a given content type
+ description: 'Required permission "content_read"'
+ operationId: getContentTypeFormController
+ parameters:
+ - name: siteId
+ in: path
+ description: site ID
+ required: true
+ schema:
+ type: string
+ - name: contentTypeId
+ in: query
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: 'OK'
+ content:
+ '*/*':
+ schema:
+ type: string
+ format: binary
+ description: The content of the form controller file
+ headers:
+ Content-Type:
+ description: The MIME type of the form controller file
+ schema:
+ type: string
+ Content-Length:
+ description: The size in bytes of the form controller file
+ schema:
+ type: integer
+ format: int64
+ '400':
+ $ref: '#/components/responses/BadRequest'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '404':
+ $ref: '#/components/responses/NotFound'
+ '500':
+ $ref: '#/components/responses/InternalServerError'
/api/2/plugin/file:
# Override the server to change the prefix
@@ -8260,6 +8262,7 @@ components:
* `CODE:` 52004, `MESSAGE:` S3 FORBIDDEN, `REMEDIAL ACTION:` Check your AWS credentials
* `CODE:` 52005, `MESSAGE:` S3 key not found, `REMEDIAL ACTION:` Check your network configuration and S3 availability
* `CODE:` 53000, `MESSAGE:` Logger not found, `REMEDIAL ACTION:` Check if you sent in the right logger name or add 'createIfAbsent=true' parameter to create the logger if it does not exist
+ * `CODE:` 54000, `MESSAGE:` The content type cannot be deleted because it is still in use by content items, `REMEDIAL ACTION:` Check if you sent in the right content type id or add 'deleteDependencies' to force delete
properties:
code:
type: integer
@@ -10515,77 +10518,79 @@ components:
ContentType:
type: object
+ description: Represents a content type in the system.
properties:
- name:
+ id:
type: string
- description: The site content name
+ description: Unique identifier for the content type.
label:
type: string
- description: The content type display name
- form:
- type: string
- description: The content type form
- formPath:
- type: string
- description: The content type form path
+ description: Human-readable label for the content type.
type:
type: string
- description: The type, e.g. page, component, etc
- contentAsFolder:
- type: boolean
- description: "Indicates whether to create content in a folder wrapper e.g. pageUrl: 101 means 101\\/index.xml instead of 101.xml"
- useRoundedFolder:
- type: boolean
- description: Indicates whether use rounded folder to arrange content
- modelInstancePath:
- type: string
- description: The path to the model instance file (WCM)
+ description: The type of content.
+ enum: [PAGE, COMPONENT, UNKNOWN]
allowedRoles:
type: array
+ description: Set of roles allowed to access this content type.
items:
- type: string
- description: The list of roles allowed
- lastUpdated:
- type: string
- description: The date the content type was last updated
- copyDependencyPattern:
+ type: object
+ description: Role allowed to access the content type.
+ properties:
+ name:
+ type: string
+ description: Name of the role.
+ deleteDependencies:
type: array
+ description: List of dependencies to delete with this content type.
items:
- type: string
- description: The list of copy association patterns
+ type: object
+ description: Dependency to delete with the content type.
+ properties:
+ pattern:
+ type: string
+ description: Pattern for the dependency to delete.
+ removeEmptyFolder:
+ type: boolean
+ description: Whether to remove the folder if it becomes empty after deletion.
+ copyDependencies:
+ type: array
+ description: List of dependencies to copy with this content type.
+ items:
+ type: object
+ description: Dependency to copy with the content type.
+ properties:
+ pattern:
+ type: string
+ description: Pattern for the dependency to copy.
+ target:
+ type: string
+ description: Target path for the copied dependency.
+ previewable:
+ type: boolean
+ description: Whether the content type is previewable.
imageThumbnail:
type: string
- description: The thumbnail image file to be displayed
+ description: Path or URL to the thumbnail image.
noThumbnail:
type: boolean
- description: Indicates whether content type has a thumbnail
+ description: Whether the content type has no thumbnail.
pathIncludes:
type: array
items:
type: string
- description: The the list of included paths
+ description: List of path patterns to include.
pathExcludes:
type: array
items:
type: string
- description: The the list of excluded paths
- nodeRef:
- type: string
- description: The configuration noderef this content type is associated with
+ description: List of path patterns to exclude.
quickCreate:
type: boolean
- description: Indicates whether the content type is available from the quick create button
+ description: Whether quick create is enabled for this content type.
quickCreatePath:
type: string
- description: The destination path pattern of content type
- deleteDependencyPattern:
- type: array
- items:
- type: string
- description: The list of delete association patterns that this content type is dependent on for deleting indexes in webproject
- previewable:
- type: boolean
- description: Indicates whether the content type is previewable
+ description: Path to use for quick create.
ContentItemV1:
type: object
diff --git a/src/main/java/org/craftercms/studio/api/v2/exception/contentType/ContentTypeUsageException.java b/src/main/java/org/craftercms/studio/api/v2/exception/contentType/ContentTypeUsageException.java
new file mode 100644
index 0000000000..e572c73a48
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/api/v2/exception/contentType/ContentTypeUsageException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.api.v2.exception.contentType;
+
+import org.craftercms.studio.api.v1.exception.ServiceLayerException;
+
+/**
+ * Exception to be thrown when trying to delete a content type that is still in use by content items
+ */
+public class ContentTypeUsageException extends ServiceLayerException {
+
+ public ContentTypeUsageException(String siteId, String contentType) {
+ super(String.format("Content type '%s' is still in use by content items in site '%s'", contentType, siteId));
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/api/v2/service/content/ContentTypeService.java b/src/main/java/org/craftercms/studio/api/v2/service/content/ContentTypeService.java
index 1edfee9494..c3b3741625 100644
--- a/src/main/java/org/craftercms/studio/api/v2/service/content/ContentTypeService.java
+++ b/src/main/java/org/craftercms/studio/api/v2/service/content/ContentTypeService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -17,9 +17,11 @@
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.studio.api.v1.exception.ServiceLayerException;
+import org.craftercms.studio.api.v1.exception.SiteNotFoundException;
import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
import org.craftercms.studio.api.v2.dal.QuickCreateItem;
+import org.craftercms.studio.model.contentType.ContentType;
import org.craftercms.studio.model.contentType.ContentTypeUsage;
import org.springframework.core.io.Resource;
@@ -109,4 +111,30 @@ void deleteContentType(String siteId, String contentType, boolean deleteDependen
* @return List of quick creatable content types
*/
List getQuickCreatableContentTypes(String siteId) throws ServiceLayerException;
+
+ /**
+ * Get all content types for the given site.
+ * @param siteId the id of the site
+ * @return a collection of content types
+ * @throws SiteNotFoundException if the site with the given id does not exist
+ */
+ Collection getAllContentTypes(String siteId) throws ServiceLayerException;
+
+ /**
+ * Get the content type configuration for a given content type id
+ *
+ * @param siteId the id of the site
+ * @param contentTypeId the id of the content type
+ * @return the content type configuration
+ */
+ ContentType getContentType(String siteId, String contentTypeId) throws ServiceLayerException;
+
+ /**
+ * Get a collection of the ids of the content types allowed for the given site and path
+ *
+ * @param siteId the id of the site
+ * @param path the path of the content item to be created
+ * @return a collection of the ids of the content types allowed for the given site and path
+ */
+ Collection getAllowedContentTypes(String siteId, String path) throws ServiceLayerException;
}
diff --git a/src/main/java/org/craftercms/studio/api/v2/utils/StudioUtils.java b/src/main/java/org/craftercms/studio/api/v2/utils/StudioUtils.java
index d78171b682..fdb6398af7 100644
--- a/src/main/java/org/craftercms/studio/api/v2/utils/StudioUtils.java
+++ b/src/main/java/org/craftercms/studio/api/v2/utils/StudioUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -21,6 +21,7 @@
import org.apache.commons.io.IOUtils;
import org.craftercms.commons.http.RequestContext;
import org.craftercms.studio.api.v1.constant.StudioConstants;
+import org.craftercms.studio.model.contentType.ContentType;
import org.dom4j.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,6 +36,7 @@
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
+import java.util.regex.Pattern;
import static org.apache.commons.io.FilenameUtils.directoryContains;
import static org.apache.commons.lang3.StringUtils.isEmpty;
@@ -116,6 +118,7 @@ public static Path getStudioTemporaryFilesRoot() {
* @throws IOException if an error occurs while creating the file
*/
public static Path createTempFile(String name) throws IOException {
+ Files.createDirectories(getStudioTemporaryFilesRoot());
return Files.createTempFile(getStudioTemporaryFilesRoot(), UUID.randomUUID().toString(), "." +
FilenameUtils.getExtension(name));
}
@@ -240,4 +243,24 @@ public static boolean underPagesRoot(String path) {
public static String movePath(String sourceRoot, String targetRoot, String sourcePath) {
return Path.of(targetRoot).resolve(Path.of(sourceRoot).relativize(Path.of(sourcePath))).toString();
}
+
+ /**
+ * Get the content type type by its id, which is determined by the content type naming convention.
+ *
+ * @param contentTypeId the content type id to check
+ * @return
+ * component if the name matches component naming convention
+ * page if the name matches page naming convention
+ * unknown if name don't match any known convention
+ *
+ */
+ public static ContentType.Type getContentTypeTypeById(String contentTypeId) {
+ if (Pattern.matches("/component/.*?", contentTypeId)) {
+ return ContentType.Type.component;
+ }
+ if (Pattern.matches("/page/.*?", contentTypeId)){
+ return ContentType.Type.page;
+ }
+ return ContentType.Type.unknown;
+ }
}
diff --git a/src/main/java/org/craftercms/studio/controller/rest/v2/ConfigurationController.java b/src/main/java/org/craftercms/studio/controller/rest/v2/ConfigurationController.java
index c443de8dcb..9ce2d8d892 100644
--- a/src/main/java/org/craftercms/studio/controller/rest/v2/ConfigurationController.java
+++ b/src/main/java/org/craftercms/studio/controller/rest/v2/ConfigurationController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2023 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,10 +16,7 @@
package org.craftercms.studio.controller.rest.v2;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import jakarta.validation.Valid;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.commons.validation.annotations.param.EsapiValidatedParam;
import org.craftercms.commons.validation.annotations.param.ValidConfigurationPath;
import org.craftercms.commons.validation.annotations.param.ValidSiteId;
@@ -28,9 +25,7 @@
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
import org.craftercms.studio.api.v2.annotation.LogExecutionTime;
import org.craftercms.studio.api.v2.service.config.ConfigurationService;
-import org.craftercms.studio.api.v2.service.content.ContentTypeService;
import org.craftercms.studio.api.v2.utils.StudioConfiguration;
-import org.craftercms.studio.api.v2.utils.StudioUtils;
import org.craftercms.studio.model.config.TranslationConfiguration;
import org.craftercms.studio.model.rest.ConfigurationHistory;
import org.craftercms.studio.model.rest.Result;
@@ -38,9 +33,6 @@
import org.craftercms.studio.model.rest.WriteConfigurationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.core.io.Resource;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -51,30 +43,28 @@
import static org.apache.commons.lang3.Strings.CS;
import static org.craftercms.commons.validation.annotations.param.EsapiValidationType.ALPHANUMERIC;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_GLOBAL_SYSTEM_SITE;
-import static org.craftercms.studio.controller.rest.v2.ResultConstants.*;
-import static org.craftercms.studio.model.rest.ApiResponse.DELETED;
+import static org.craftercms.studio.controller.rest.v2.RequestMappingConstants.*;
+import static org.craftercms.studio.controller.rest.v2.ResultConstants.RESULT_KEY_CONFIG;
+import static org.craftercms.studio.controller.rest.v2.ResultConstants.RESULT_KEY_HISTORY;
import static org.craftercms.studio.model.rest.ApiResponse.OK;
@Validated
@RestController
-@RequestMapping("/api/2/configuration")
+@RequestMapping(API_2 + CONFIGURATION)
public class ConfigurationController {
private final ConfigurationService configurationService;
private final StudioConfiguration studioConfiguration;
- private final ContentTypeService contentTypeService;
@SuppressWarnings("unused")
private static final Logger logger = LoggerFactory.getLogger(ConfigurationController.class);
- @ConstructorProperties({"configurationService", "studioConfiguration", "contentTypeService"})
- public ConfigurationController(ConfigurationService configurationService, StudioConfiguration studioConfiguration,
- ContentTypeService contentTypeService) {
+ @ConstructorProperties({"configurationService", "studioConfiguration"})
+ public ConfigurationController(ConfigurationService configurationService, StudioConfiguration studioConfiguration) {
this.configurationService = configurationService;
this.studioConfiguration = studioConfiguration;
- this.contentTypeService = contentTypeService;
}
- @GetMapping("clear_cache")
+ @GetMapping(CLEAR_CACHE)
public Result clearCache(@ValidSiteId @RequestParam String siteId) {
configurationService.invalidateConfiguration(siteId);
var result = new Result();
@@ -82,7 +72,7 @@ public Result clearCache(@ValidSiteId @RequestParam String siteId) {
return result;
}
- @GetMapping("/get_configuration")
+ @GetMapping(GET_CONFIGURATION)
@LogExecutionTime
public ResultOne getConfiguration(@ValidSiteId @RequestParam(name = "siteId", required = true) String siteId,
@EsapiValidatedParam(type = ALPHANUMERIC) @RequestParam(name = "module", required = true) String module,
@@ -102,7 +92,7 @@ public ResultOne getConfiguration(@ValidSiteId @RequestParam(name = "sit
return result;
}
- @PostMapping("/write_configuration")
+ @PostMapping(WRITE_CONFIGURATION)
public Result writeConfiguration(@Validated @RequestBody WriteConfigurationRequest wcRequest)
throws ServiceLayerException, UserNotFoundException, AuthenticationException {
InputStream is = IOUtils.toInputStream(wcRequest.getContent(), UTF_8);
@@ -118,7 +108,7 @@ public Result writeConfiguration(@Validated @RequestBody WriteConfigurationReque
return result;
}
- @GetMapping("/get_configuration_history")
+ @GetMapping(GET_CONFIGURATION_HISTORY)
public ResultOne getConfigurationHistory(@ValidSiteId @RequestParam(name = "siteId", required = true) String siteId,
@EsapiValidatedParam(type = ALPHANUMERIC) @RequestParam(name = "module", required = true) String module,
@ValidConfigurationPath @RequestParam(name = "path", required = true) String path,
@@ -132,7 +122,7 @@ public ResultOne getConfigurationHistory(@ValidSiteId @Req
return result;
}
- @GetMapping("translation")
+ @GetMapping(TRANSLATION)
public ResultOne getTranslationConfiguration(@ValidSiteId @RequestParam String siteId) throws ServiceLayerException {
ResultOne result = new ResultOne<>();
result.setEntity(RESULT_KEY_CONFIG, configurationService.getTranslationConfiguration(siteId));
@@ -140,87 +130,4 @@ public ResultOne getTranslationConfiguration(@ValidSit
return result;
}
- @GetMapping("content-type/usage")
- public ResultOne getContentTypeUsage(@ValidSiteId @RequestParam String siteId,
- @ValidConfigurationPath @RequestParam String contentType)
- throws Exception {
- var result = new ResultOne<>();
- result.setResponse(OK);
- result.setEntity(RESULT_KEY_USAGE, contentTypeService.getContentTypeUsage(siteId, contentType));
-
- return result;
- }
-
- @GetMapping("content-type/form_controller")
- public ResponseEntity getContentTypeFormController(@ValidSiteId @RequestParam String siteId,
- @ValidConfigurationPath @RequestParam String contentTypeId) throws ServiceLayerException {
- ImmutablePair resource = contentTypeService.getContentTypeFormController(siteId, contentTypeId);
- return getResourceResponse(resource.getKey(), resource.getValue());
- }
-
- @GetMapping("content-type/preview_image")
- public ResponseEntity getContentTypePreviewImage(@ValidSiteId @RequestParam String siteId,
- @ValidConfigurationPath @RequestParam String contentTypeId)
- throws ServiceLayerException {
- ImmutablePair resource = contentTypeService.getContentTypePreviewImage(siteId, contentTypeId);
- return getResourceResponse(resource.getKey(), resource.getValue());
- }
-
- private ResponseEntity getResourceResponse(String name, Resource resource) {
- String mimeType = StudioUtils.getMimeType(name);
-
- return ResponseEntity
- .ok()
- .header(HttpHeaders.CONTENT_TYPE, mimeType)
- .body(resource);
- }
-
- @PostMapping("content-type/delete")
- public Result deleteContentType(@RequestBody @Valid DeleteContentTypeRequest request)
- throws ServiceLayerException, AuthenticationException, UserNotFoundException {
- contentTypeService.deleteContentType(request.getSiteId(), request.getContentType(),
- request.isDeleteDependencies());
- var result = new Result();
- result.setResponse(DELETED);
-
- return result;
- }
-
- @JsonIgnoreProperties
- public static class DeleteContentTypeRequest {
-
- @ValidSiteId
- protected String siteId;
-
- @ValidConfigurationPath
- protected String contentType;
-
- protected boolean deleteDependencies;
-
- public String getSiteId() {
- return siteId;
- }
-
- public void setSiteId(String siteId) {
- this.siteId = siteId;
- }
-
- public String getContentType() {
- return contentType;
- }
-
- public void setContentType(String contentType) {
- this.contentType = contentType;
- }
-
- public boolean isDeleteDependencies() {
- return deleteDependencies;
- }
-
- public void setDeleteDependencies(boolean deleteDependencies) {
- this.deleteDependencies = deleteDependencies;
- }
-
- }
-
}
diff --git a/src/main/java/org/craftercms/studio/controller/rest/v2/ContentTypeController.java b/src/main/java/org/craftercms/studio/controller/rest/v2/ContentTypeController.java
new file mode 100644
index 0000000000..6891bcff47
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/controller/rest/v2/ContentTypeController.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.controller.rest.v2;
+
+import jakarta.validation.Valid;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.craftercms.commons.validation.annotations.param.ValidConfigurationPath;
+import org.craftercms.commons.validation.annotations.param.ValidExistingContentPath;
+import org.craftercms.commons.validation.annotations.param.ValidSiteId;
+import org.craftercms.commons.validation.annotations.param.ValidateSecurePathParam;
+import org.craftercms.studio.api.v1.exception.ServiceLayerException;
+import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
+import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
+import org.craftercms.studio.api.v2.service.content.ContentTypeService;
+import org.craftercms.studio.api.v2.utils.StudioUtils;
+import org.craftercms.studio.model.contentType.ContentType;
+import org.craftercms.studio.model.rest.Result;
+import org.craftercms.studio.model.rest.ResultList;
+import org.craftercms.studio.model.rest.ResultOne;
+import org.craftercms.studio.model.rest.contentType.DeleteContentTypeRequest;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.beans.ConstructorProperties;
+import java.util.Collection;
+import java.util.List;
+
+import static org.apache.commons.lang3.StringUtils.isEmpty;
+import static org.craftercms.studio.controller.rest.v2.RequestMappingConstants.*;
+import static org.craftercms.studio.controller.rest.v2.ResultConstants.*;
+import static org.craftercms.studio.model.rest.ApiResponse.DELETED;
+import static org.craftercms.studio.model.rest.ApiResponse.OK;
+
+@Validated
+@RestController
+@RequestMapping(API_2 + CONFIGURATION + CONTENT_TYPES + SITE_ID)
+public class ContentTypeController {
+ private final ContentTypeService contentTypeService;
+
+ @ConstructorProperties("contentTypeService")
+ public ContentTypeController(ContentTypeService contentTypeService) {
+ this.contentTypeService = contentTypeService;
+ }
+
+ @GetMapping(USAGE)
+ public ResultOne getContentTypeUsage(@ValidSiteId @PathVariable String siteId,
+ @ValidConfigurationPath @RequestParam String contentType)
+ throws Exception {
+ var result = new ResultOne<>();
+ result.setResponse(OK);
+ result.setEntity(RESULT_KEY_USAGE, contentTypeService.getContentTypeUsage(siteId, contentType));
+
+ return result;
+ }
+
+ @GetMapping(FORM_CONTROLLER)
+ public ResponseEntity getContentTypeFormController(@ValidSiteId @PathVariable String siteId,
+ @ValidConfigurationPath @RequestParam String contentTypeId) throws ServiceLayerException {
+ ImmutablePair resource = contentTypeService.getContentTypeFormController(siteId, contentTypeId);
+ return getResourceResponse(resource.getKey(), resource.getValue());
+ }
+
+ @GetMapping(PREVIEW_IMAGE)
+ public ResponseEntity getContentTypePreviewImage(@ValidSiteId @PathVariable String siteId,
+ @ValidConfigurationPath @ValidateSecurePathParam @RequestParam String contentTypeId)
+ throws ServiceLayerException {
+ ImmutablePair resource = contentTypeService.getContentTypePreviewImage(siteId, contentTypeId);
+ return getResourceResponse(resource.getKey(), resource.getValue());
+ }
+
+ @GetMapping
+ public ResultList getContentTypes(@ValidSiteId @PathVariable String siteId,
+ @ValidConfigurationPath @RequestParam(required = false) String contentTypeId) throws ServiceLayerException {
+ var result = new ResultList();
+ result.setResponse(OK);
+
+ Collection contentTypes;
+ if (isEmpty(contentTypeId)) {
+ contentTypes = contentTypeService.getAllContentTypes(siteId);
+ } else {
+ contentTypes = List.of(contentTypeService.getContentType(siteId, contentTypeId));
+ }
+ result.setEntities(RESULT_KEY_CONTENT_TYPES, contentTypes);
+ return result;
+ }
+
+ @GetMapping(ALLOWED_TYPES)
+ public ResultList getAllowedContentTypes(@ValidSiteId @PathVariable String siteId, @ValidExistingContentPath @RequestParam String path) throws ServiceLayerException {
+ ResultList result = new ResultList<>();
+ result.setResponse(OK);
+ result.setEntities(RESULT_KEY_ALLOWED_TYPES, contentTypeService.getAllowedContentTypes(siteId, path));
+ return result;
+ }
+
+ @DeleteMapping
+ public Result deleteContentType(@ValidSiteId @PathVariable String siteId, @RequestBody @Valid DeleteContentTypeRequest request)
+ throws ServiceLayerException, AuthenticationException, UserNotFoundException {
+ contentTypeService.deleteContentType(siteId, request.getContentType(),
+ request.isDeleteDependencies());
+ var result = new Result();
+ result.setResponse(DELETED);
+ return result;
+ }
+
+ private ResponseEntity getResourceResponse(String name, Resource resource) {
+ String mimeType = StudioUtils.getMimeType(name);
+
+ return ResponseEntity
+ .ok()
+ .header(HttpHeaders.CONTENT_TYPE, mimeType)
+ .body(resource);
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.java b/src/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.java
index 9c079d3f41..9e83e1fb15 100644
--- a/src/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.java
+++ b/src/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.java
@@ -41,6 +41,7 @@
import org.craftercms.studio.api.v2.exception.content.ContentInPublishQueueException;
import org.craftercms.studio.api.v2.exception.content.ContentLockedByAnotherUserException;
import org.craftercms.studio.api.v2.exception.content.ContentMoveInvalidLocation;
+import org.craftercms.studio.api.v2.exception.contentType.ContentTypeUsageException;
import org.craftercms.studio.api.v2.exception.git.MergeInProgressException;
import org.craftercms.studio.api.v2.exception.git.NoMergeStateException;
import org.craftercms.studio.api.v2.exception.logger.LoggerNotFoundException;
@@ -681,6 +682,14 @@ public Result handleException(HttpServletRequest request, NoMergeStateException
return handleExceptionInternal(request, e, response);
}
+ @ExceptionHandler
+ @ResponseStatus(CONFLICT)
+ public Result handleException(HttpServletRequest request, ContentTypeUsageException e) {
+ ApiResponse response = new ApiResponse(ApiResponse.CONTENT_TYPE_IN_USE);
+ response.setMessage(e.getMessage());
+ return handleExceptionInternal(request, e, response);
+ }
+
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleException(HttpServletRequest request, Exception e) {
diff --git a/src/main/java/org/craftercms/studio/controller/rest/v2/RequestMappingConstants.java b/src/main/java/org/craftercms/studio/controller/rest/v2/RequestMappingConstants.java
index 7d9c34d073..960d081ece 100644
--- a/src/main/java/org/craftercms/studio/controller/rest/v2/RequestMappingConstants.java
+++ b/src/main/java/org/craftercms/studio/controller/rest/v2/RequestMappingConstants.java
@@ -164,6 +164,20 @@ public final class RequestMappingConstants {
*/
public static final String MONITOR = "/monitor";
+ /**
+ * Configuration Controller
+ */
+ public static final String CONFIGURATION = "/configuration";
+ public static final String CLEAR_CACHE = "/clear_cache";
+ public static final String GET_CONFIGURATION = "/get_configuration";
+ public static final String WRITE_CONFIGURATION = "/write_configuration";
+ public static final String GET_CONFIGURATION_HISTORY = "/get_configuration_history";
+ public static final String TRANSLATION = "/translation";
+ public static final String CONTENT_TYPES = "/content_types";
+ public static final String USAGE = "/usage";
+ public static final String PREVIEW_IMAGE = "/preview_image";
+ public static final String FORM_CONTROLLER = "/form_controller";
+ public static final String ALLOWED_TYPES = "/allowed_types";
private RequestMappingConstants() {
}
diff --git a/src/main/java/org/craftercms/studio/controller/rest/v2/ResultConstants.java b/src/main/java/org/craftercms/studio/controller/rest/v2/ResultConstants.java
index 2040faafa4..8f106760c9 100644
--- a/src/main/java/org/craftercms/studio/controller/rest/v2/ResultConstants.java
+++ b/src/main/java/org/craftercms/studio/controller/rest/v2/ResultConstants.java
@@ -117,6 +117,12 @@ public final class ResultConstants {
*/
public static final String RESULT_KEY_MONITORS = "monitors";
+ /**
+ * Content types controller
+ */
+ public static final String RESULT_KEY_CONTENT_TYPES = "contentTypes";
+ public static final String RESULT_KEY_ALLOWED_TYPES = "allowedTypes";
+
/**
* Exception Handler
*/
diff --git a/src/main/java/org/craftercms/studio/impl/v1/service/configuration/ContentTypesConfigImpl.java b/src/main/java/org/craftercms/studio/impl/v1/service/configuration/ContentTypesConfigImpl.java
index 0c9756b345..b17903aac1 100644
--- a/src/main/java/org/craftercms/studio/impl/v1/service/configuration/ContentTypesConfigImpl.java
+++ b/src/main/java/org/craftercms/studio/impl/v1/service/configuration/ContentTypesConfigImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -33,6 +33,7 @@
import org.craftercms.studio.api.v2.dal.security.NormalizedRole;
import org.craftercms.studio.api.v2.service.config.ConfigurationService;
import org.craftercms.studio.api.v2.utils.StudioConfiguration;
+import org.craftercms.studio.api.v2.utils.StudioUtils;
import org.craftercms.studio.impl.v1.util.ContentFormatUtils;
import org.craftercms.studio.impl.v2.utils.DateUtils;
import org.dom4j.Document;
@@ -51,6 +52,7 @@
import static org.craftercms.studio.api.v1.constant.StudioConstants.FILE_SEPARATOR;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_FILE_NAME;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_PATH;
+import static org.craftercms.studio.api.v2.utils.StudioUtils.getContentTypeTypeById;
/**
* @author Dejan Brkic
@@ -127,7 +129,7 @@ public ContentTypeConfigTO loadConfiguration(@ValidateStringParam String site,
loadDeleteDependencies(contentTypeConfig, root.selectNodes("delete-dependencies/delete-dependency"));
loadCopyDependencyPatterns(contentTypeConfig, root.selectNodes("copy-dependencies/copy-dependency"));
contentTypeConfig.setLastUpdated(DateUtils.getCurrentTime());
- contentTypeConfig.setType(getContentTypeTypeByName(name));
+ contentTypeConfig.setType(getContentTypeTypeById(name).name());
boolean quickCreate = ContentFormatUtils.getBooleanValue(root.valueOf(QUICK_CREATE));
contentTypeConfig.setQuickCreate(quickCreate);
contentTypeConfig.setQuickCreatePath(root.valueOf(QUICK_CREATE_PATH));
@@ -172,26 +174,6 @@ protected void loadDeleteDependencies(ContentTypeConfigTO contentTypeConfig, Lis
}
}
- /**
- * Checks name for naming convention.
- *
- * @param name Name to be check
- * @return
- * component if the name matches component naming convention
- * page if the name matches page naming convention
- * unknown if name don't match any known convention
- *
- */
- private String getContentTypeTypeByName(String name) {
- if (Pattern.matches("/component/.*?", name)) {
- return "component";
- } else if (Pattern.matches("/page/.*?", name))
- return "page";
- else {
- return "unknown";
- }
- }
-
/**
* get paths
*
diff --git a/src/main/java/org/craftercms/studio/impl/v2/service/content/ContentTypeServiceImpl.java b/src/main/java/org/craftercms/studio/impl/v2/service/content/ContentTypeServiceImpl.java
index 108c015ece..f0bd472fb3 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/service/content/ContentTypeServiceImpl.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/service/content/ContentTypeServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -21,10 +21,12 @@
import org.craftercms.studio.api.v1.exception.ServiceLayerException;
import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
+import org.craftercms.studio.api.v2.annotation.ContentPath;
import org.craftercms.studio.api.v2.annotation.RequireSiteReady;
import org.craftercms.studio.api.v2.annotation.SiteId;
import org.craftercms.studio.api.v2.dal.QuickCreateItem;
import org.craftercms.studio.api.v2.service.content.ContentTypeService;
+import org.craftercms.studio.model.contentType.ContentType;
import org.craftercms.studio.model.contentType.ContentTypeUsage;
import org.springframework.core.io.Resource;
@@ -116,6 +118,27 @@ public List getQuickCreatableContentTypes(@SiteId String siteId
return contentTypeServiceInternal.getQuickCreatableContentTypes(siteId);
}
+ @Override
+ @RequireSiteReady
+ @HasPermission(type = DefaultPermission.class, action = PERMISSION_CONTENT_READ)
+ public Collection getAllContentTypes(@SiteId String siteId) throws ServiceLayerException {
+ return contentTypeServiceInternal.getAllContentTypes(siteId);
+ }
+
+ @Override
+ @RequireSiteReady
+ @HasPermission(type = DefaultPermission.class, action = PERMISSION_CONTENT_READ)
+ public ContentType getContentType(@SiteId String siteId, String contentTypeId) throws ServiceLayerException {
+ return contentTypeServiceInternal.getContentType(siteId, contentTypeId);
+ }
+
+ @Override
+ @RequireSiteReady
+ @HasPermission(type = DefaultPermission.class, action = PERMISSION_CONTENT_READ)
+ public Collection getAllowedContentTypes(@SiteId String siteId, @ContentPath String path) throws ServiceLayerException {
+ return contentTypeServiceInternal.getAllowedContentTypes(siteId, path);
+ }
+
@Override
@HasPermission(type = DefaultPermission.class, action = PERMISSION_READ_CONFIGURATION)
public String getContentTypeControllerPath(String contentTypeId) {
diff --git a/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentServiceInternalImpl.java b/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentServiceInternalImpl.java
index 3490cb9cce..0096aafad8 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentServiceInternalImpl.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentServiceInternalImpl.java
@@ -94,7 +94,6 @@
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImpl.java b/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImpl.java
index bbfb95e46e..b8c3fd95ec 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImpl.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,29 +16,32 @@
package org.craftercms.studio.impl.v2.service.content.internal;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.google.common.cache.Cache;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.commons.lang.UrlUtils;
-import org.craftercms.commons.validation.annotations.param.ValidateSecurePathParam;
import org.craftercms.studio.api.v1.exception.ContentNotFoundException;
import org.craftercms.studio.api.v1.exception.ServiceLayerException;
import org.craftercms.studio.api.v1.exception.SiteNotFoundException;
import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
-import org.craftercms.studio.api.v1.service.content.ContentTypeService;
import org.craftercms.studio.api.v1.service.security.SecurityService;
-import org.craftercms.studio.api.v1.to.ContentTypeConfigTO;
import org.craftercms.studio.api.v2.annotation.RequireSiteExists;
import org.craftercms.studio.api.v2.annotation.SiteId;
import org.craftercms.studio.api.v2.dal.ItemDAO;
import org.craftercms.studio.api.v2.dal.QuickCreateItem;
import org.craftercms.studio.api.v2.dal.item.LightItem;
+import org.craftercms.studio.api.v2.exception.configuration.ConfigurationException;
+import org.craftercms.studio.api.v2.exception.contentType.ContentTypeUsageException;
import org.craftercms.studio.api.v2.service.config.ConfigurationService;
import org.craftercms.studio.api.v2.service.content.ContentService;
import org.craftercms.studio.api.v2.service.publish.PublishService;
import org.craftercms.studio.api.v2.utils.GitRepositoryHelper;
+import org.craftercms.studio.impl.v2.utils.Wrapper;
import org.craftercms.studio.impl.v2.utils.security.SecurityUtils;
+import org.craftercms.studio.model.contentType.ContentType;
import org.craftercms.studio.model.contentType.ContentTypeUsage;
import org.dom4j.Document;
import org.dom4j.Node;
@@ -51,32 +54,34 @@
import java.beans.ConstructorProperties;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
import static java.lang.String.format;
import static java.nio.file.Files.walkFileTree;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
+import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.apache.commons.io.FilenameUtils.normalize;
import static org.apache.commons.lang3.RegExUtils.replaceAll;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.isNotEmpty;
+import static org.apache.commons.lang3.Strings.CI;
import static org.craftercms.studio.api.v1.constant.GitRepositories.SANDBOX;
import static org.craftercms.studio.api.v1.constant.StudioConstants.*;
import static org.craftercms.studio.permissions.StudioPermissionsConstants.PERMISSION_CONTENT_CREATE;
+/**
+ * Internal implementation of {@link org.craftercms.studio.api.v2.service.content.ContentTypeService}.
+ */
public class ContentTypeServiceInternalImpl implements org.craftercms.studio.api.v2.service.content.ContentTypeService {
private static final Logger logger = LoggerFactory.getLogger(ContentTypeServiceInternalImpl.class);
- protected final ContentTypeService contentTypeService;
protected final SecurityService securityService;
protected final ConfigurationService configurationService;
protected final ItemDAO itemDao;
@@ -85,7 +90,6 @@ public class ContentTypeServiceInternalImpl implements org.craftercms.studio.api
protected final String contentTypeBasePathPattern;
protected final String contentTypesRootPath;
protected final String contentTypeDefinitionFilename;
- protected final String contentTypeConfigFilename;
protected final String templateXPath;
protected final String controllerPattern;
protected final String controllerFormat;
@@ -93,27 +97,28 @@ public class ContentTypeServiceInternalImpl implements org.craftercms.studio.api
protected final String defaultPreviewImagePath;
protected final String formControllerFilePath;
private final GitRepositoryHelper gitRepositoryHelper;
-
- @ConstructorProperties({"contentTypeService", "securityService", "configurationService", "itemDao",
- "contentTypeBasePathPattern", "contentTypeDefinitionFilename", "contentTypeConfigFilename",
- "contentTypesRootPath",
- "templateXPath", "controllerPattern", "controllerFormat", "previewImageXPath", "defaultPreviewImagePath",
- "formControllerFilePath", "gitRepositoryHelper"})
- public ContentTypeServiceInternalImpl(ContentTypeService contentTypeService, SecurityService securityService,
+ private final Cache cache;
+ private final XmlMapper xmlMapper;
+
+ @ConstructorProperties({"securityService", "configurationService", "itemDao",
+ "contentTypeBasePathPattern", "contentTypeDefinitionFilename",
+ "contentTypesRootPath",
+ "templateXPath", "controllerPattern", "controllerFormat", "previewImageXPath", "defaultPreviewImagePath",
+ "formControllerFilePath", "gitRepositoryHelper",
+ "cache"})
+ public ContentTypeServiceInternalImpl(SecurityService securityService,
ConfigurationService configurationService, ItemDAO itemDao, String contentTypeBasePathPattern,
- String contentTypeDefinitionFilename, String contentTypeConfigFilename,
+ String contentTypeDefinitionFilename,
String contentTypesRootPath, String templateXPath,
String controllerPattern, String controllerFormat,
String previewImageXPath, String defaultPreviewImagePath,
- String formControllerFilePath,
- GitRepositoryHelper gitRepositoryHelper) {
- this.contentTypeService = contentTypeService;
+ String formControllerFilePath, GitRepositoryHelper gitRepositoryHelper,
+ Cache cache) {
this.securityService = securityService;
this.configurationService = configurationService;
this.itemDao = itemDao;
this.contentTypeBasePathPattern = contentTypeBasePathPattern;
this.contentTypeDefinitionFilename = contentTypeDefinitionFilename;
- this.contentTypeConfigFilename = contentTypeConfigFilename;
this.contentTypesRootPath = contentTypesRootPath;
this.templateXPath = templateXPath;
this.controllerPattern = controllerPattern;
@@ -122,6 +127,8 @@ public ContentTypeServiceInternalImpl(ContentTypeService contentTypeService, Sec
this.defaultPreviewImagePath = defaultPreviewImagePath;
this.formControllerFilePath = formControllerFilePath;
this.gitRepositoryHelper = gitRepositoryHelper;
+ this.cache = cache;
+ this.xmlMapper = new XmlMapper();
}
public void setContentService(ContentService contentService) {
@@ -130,26 +137,109 @@ public void setContentService(ContentService contentService) {
@Override
public List getQuickCreatableContentTypes(String siteId) throws ServiceLayerException {
- return contentTypeService.getAllContentTypes(siteId, true).stream()
- .filter(ContentTypeConfigTO::isQuickCreate)
- .filter(contentType -> {
- try {
- return securityService.getUserPermissions(siteId, contentType.getQuickCreatePath(), SecurityUtils.getCurrentUsername())
- .contains(PERMISSION_CONTENT_CREATE);
- } catch (SiteNotFoundException e) {
- // This should never happen. If the site does not exist then getAllContentTypes() call above should have thrown an exception
- return false;
+ return getAllContentTypes(siteId).stream()
+ .filter(ContentType::isQuickCreate)
+ .filter(contentType -> {
+ try {
+ return securityService.getUserPermissions(siteId, contentType.getQuickCreatePath(), SecurityUtils.getCurrentUsername())
+ .contains(PERMISSION_CONTENT_CREATE);
+ } catch (SiteNotFoundException e) {
+ // This should never happen. If the site does not exist then getAllContentTypes() call above should have thrown an exception
+ return false;
+ }
+ })
+ .map(contentType -> {
+ QuickCreateItem item = new QuickCreateItem();
+ item.setSiteId(siteId);
+ item.setContentTypeId(contentType.getId());
+ item.setLabel(contentType.getLabel());
+ item.setPath(contentType.getQuickCreatePath());
+ return item;
+ })
+ .collect(toList());
+ }
+
+ @Override
+ public Collection getAllContentTypes(String siteId) throws ServiceLayerException {
+ Collection contentTypes = new ArrayList<>();
+
+ Path repoRootPath = gitRepositoryHelper.buildRepoPath(SANDBOX, siteId);
+ Path contentTypesRepoPath = repoRootPath.resolve(gitRepositoryHelper.getGitPath(contentTypesRootPath));
+ try {
+ Wrapper fileVisitorException = new Wrapper<>();
+ walkFileTree(contentTypesRepoPath, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (!file.getFileName().toString().equals(contentTypeDefinitionFilename)) {
+ return FileVisitResult.CONTINUE;
+ }
+ try {
+ contentTypes.add(getContentType(siteId, contentTypesRepoPath.relativize(file.getParent()).toString()));
+ } catch (ServiceLayerException e) {
+ fileVisitorException.set(e);
+ return FileVisitResult.TERMINATE;
+ }
+ return FileVisitResult.SKIP_SIBLINGS;
}
- })
- .map(contentType -> {
- QuickCreateItem item = new QuickCreateItem();
- item.setSiteId(siteId);
- item.setContentTypeId(contentType.getForm());
- item.setLabel(contentType.getLabel());
- item.setPath(contentType.getQuickCreatePath());
- return item;
- })
- .collect(toList());
+ });
+ if (fileVisitorException.hasValue()) {
+ throw fileVisitorException.get();
+ }
+ } catch (Exception e) {
+ throw new ServiceLayerException(format("Failed to retrieve content types for site '%s'", siteId), e);
+ }
+
+ return contentTypes;
+ }
+
+ @Override
+ public ContentType getContentType(String siteId, String contentTypeId) throws ServiceLayerException {
+ String configFileFullPath = getContentTypeFormPath(contentTypeId);
+ var cacheKey = configurationService.getCacheKey(siteId, null, configFileFullPath,
+ null, "object");
+ ContentType contentType = cache.getIfPresent(cacheKey);
+ if (contentType == null) {
+ logger.debug("Cache miss for key '{}'", cacheKey);
+ contentType = loadContentType(siteId, contentTypeId);
+ cache.put(cacheKey, contentType);
+ }
+
+ return contentType;
+ }
+
+ /**
+ * Get content type form-definition.xml full path in content repository
+ */
+ private String getContentTypeFormPath(String contentTypeId) {
+ String siteConfigPath = getContentTypePath(contentTypeId);
+ String configFileFullPath = siteConfigPath + FILE_SEPARATOR + contentTypeDefinitionFilename;
+ return configFileFullPath;
+ }
+
+ /**
+ * Load content type form-definition.xml from content repository and map it to ContentType object
+ *
+ * @param siteId the site id
+ * @param contentTypeId the content type id
+ * @return the ContentType object mapped from form-definition.xml
+ * @throws ConfigurationException if there is any error reading the content type configuration
+ */
+ protected ContentType loadContentType(String siteId, String contentTypeId) throws ConfigurationException, ContentNotFoundException {
+ InputStream configurationAsStream = contentService.getContent(siteId, getContentTypeFormPath(contentTypeId));
+ try {
+ return xmlMapper.readValue(configurationAsStream, ContentType.class);
+ } catch (IOException e) {
+ throw new ConfigurationException(format("Failed to read content type configuration for content type '%s' in site '%s'", contentTypeId, siteId), e);
+ }
+ }
+
+ @Override
+ public Collection getAllowedContentTypes(String siteId, String path) throws ServiceLayerException {
+ return getAllContentTypes(siteId).stream()
+ .filter(ct -> isEmpty(ct.getPathIncludes()) || ct.getPathIncludes().stream().anyMatch(path::matches))
+ .filter(ct -> ct.getPathExcludes().stream().noneMatch(path::matches))
+ .map(ContentType::getId)
+ .collect(toList());
}
@Override
@@ -167,21 +257,21 @@ public ContentTypeUsage getContentTypeUsage(String siteId, String contentType) t
List items = itemDao.getContentTypeUsages(siteId, contentType, scriptPath);
usages.setContent(items.stream()
- .filter(i -> equalsAnyIgnoreCase(i.getMetadata().systemType(), CONTENT_TYPE_PAGE, CONTENT_TYPE_COMPONENT))
- .map(LightItem::getPath)
- .collect(toList()));
+ .filter(i -> CI.equalsAny(i.getMetadata().systemType(), CONTENT_TYPE_PAGE, CONTENT_TYPE_COMPONENT))
+ .map(LightItem::getPath)
+ .collect(toList()));
usages.setScripts(items.stream()
- .filter(i -> equalsIgnoreCase(i.getMetadata().systemType(), (CONTENT_TYPE_SCRIPT)))
- .map(LightItem::getPath)
- .collect(toList()));
+ .filter(i -> CI.equals(i.getMetadata().systemType(), (CONTENT_TYPE_SCRIPT)))
+ .map(LightItem::getPath)
+ .collect(toList()));
return usages;
}
@Override
public ImmutablePair getContentTypePreviewImage(String siteId,
- @ValidateSecurePathParam String contentTypeId) throws ServiceLayerException {
+ String contentTypeId) throws ServiceLayerException {
String filename = getContentTypePreviewImageFilename(siteId, contentTypeId);
boolean hasPreviewImage = isNotEmpty(filename) && !filename.equals("undefined"); // form-definition could have undefined value for imageThumbnail
@@ -205,15 +295,14 @@ public ImmutablePair getContentTypeFormController(String siteI
@Override
public void deleteContentType(String siteId, String contentType, boolean deleteDependencies)
- throws ServiceLayerException, AuthenticationException, UserNotFoundException {
+ throws ServiceLayerException, AuthenticationException, UserNotFoundException {
ContentTypeUsage usage = getContentTypeUsage(siteId, contentType);
var files = new HashSet();
if (CollectionUtils.isNotEmpty(usage.getContent())) {
if (!deleteDependencies) {
- throw new ServiceLayerException("The content-type " + contentType + " in site " + siteId +
- " can't be deleted because there is content using it");
+ throw new ContentTypeUsageException(siteId, contentType);
}
files.addAll(usage.getContent());
@@ -276,16 +365,16 @@ private FileVisitResult visitContentTypeFile(final Path file, final List
}
protected String getContentTypePath(String contentType) {
- return normalize(contentTypeBasePathPattern.replaceFirst("\\{content-type}", contentType));
+ return normalize(contentTypeBasePathPattern.replaceFirst(PATTERN_CONTENT_TYPE, contentType));
}
/**
* Get preview image filename extract from form-definition.xml
*
- * @param siteId
- * @param contentTypeId
+ * @param siteId the site id
+ * @param contentTypeId the content type id
* @return preview image filename
- * @throws ServiceLayerException
+ * @throws ServiceLayerException if there is any error reading the content type definition
*/
protected String getContentTypePreviewImageFilename(String siteId, String contentTypeId) throws ServiceLayerException {
Document definition = getFormDefinitionDocument(siteId, contentTypeId);
@@ -302,10 +391,10 @@ protected String getContentTypePreviewImageFilename(String siteId, String conten
/**
* Get form-definition.xml as Document of a content type
*
- * @param siteId
- * @param contentTypeId
+ * @param siteId the site id
+ * @param contentTypeId the content type id
* @return Document of form-definition.xml
- * @throws ServiceLayerException
+ * @throws ServiceLayerException if there is any error reading the content type definition or if the definition file does not exist
*/
@RequireSiteExists
protected Document getFormDefinitionDocument(@SiteId String siteId, String contentTypeId) throws ServiceLayerException {
diff --git a/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgrader.java b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgrader.java
new file mode 100644
index 0000000000..2a4644de7a
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgrader.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.impl.v2.upgrade.operations.contentType;
+
+import org.apache.commons.collections4.ListUtils;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.craftercms.commons.upgrade.exception.UpgradeException;
+import org.craftercms.studio.api.v2.utils.StudioConfiguration;
+import org.craftercms.studio.impl.v2.upgrade.StudioUpgradeContext;
+import org.craftercms.studio.impl.v2.upgrade.operations.site.BatchXsltFileUpgradeOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.beans.ConstructorProperties;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_FILE_NAME;
+
+/**
+ * Upgrader to merge config.xml into form-definition.xml files for content types.
+ */
+public class ContentTypeConfigMergeUpgrader extends BatchXsltFileUpgradeOperation {
+ private static final Logger logger = LoggerFactory.getLogger(ContentTypeConfigMergeUpgrader.class);
+
+ protected static final String CONFIG_FILE_PARAM = "configFileName";
+ private String configFileName;
+
+ @ConstructorProperties("studioConfiguration")
+ public ContentTypeConfigMergeUpgrader(StudioConfiguration studioConfiguration) {
+ super(studioConfiguration);
+ }
+
+ @Override
+ protected void doInit(final HierarchicalConfiguration config) {
+ super.doInit(config);
+ configFileName = studioConfiguration.getProperty(CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_FILE_NAME);
+ }
+
+ @Override
+ public void doExecute(StudioUpgradeContext context) throws UpgradeException {
+ super.doExecute(context);
+ Path repositoryRoot = context.getRepositoryPath();
+ for (String deletedFile : ListUtils.emptyIfNull(deletedFiles)) {
+ logger.info("Deleting config file after merging: {}", deletedFile);
+ try {
+ Files.deleteIfExists(repositoryRoot.resolve(deletedFile));
+ } catch (IOException e) {
+ throw new UpgradeException("Error deleting config file after merging: " + deletedFile, e);
+ }
+ }
+ }
+
+ @Override
+ protected void executeTemplate(StudioUpgradeContext context, String formDefinitionPath, OutputStream os) throws UpgradeException {
+ logger.info("Executing config merge template for form definition: {}", formDefinitionPath);
+ super.executeTemplate(context, formDefinitionPath, os);
+
+ Path configFilePath = Path.of(formDefinitionPath).resolveSibling(configFileName);
+ trackDeletedFiles(configFilePath.toString());
+ }
+
+ @Override
+ protected Map getTemplateParameters(StudioUpgradeContext context, String formDefinitionPath) {
+ Map params = new HashMap<>(super.getTemplateParameters(context, formDefinitionPath));
+ params.put(CONFIG_FILE_PARAM, Path.of(formDefinitionPath).resolveSibling(configFileName));
+ return params;
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AbstractXsltFileUpgradeOperation.java b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AbstractXsltFileUpgradeOperation.java
index db74f7629f..008eb79d57 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AbstractXsltFileUpgradeOperation.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AbstractXsltFileUpgradeOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -82,7 +82,7 @@ protected void executeTemplate(StudioUpgradeContext context, String path, Output
try (InputStream templateIs = template.getInputStream();
InputStream sourceIs = Files.newInputStream(file)) {
logger.info("Apply the XSLT template '{}' to file '{}' in site '{}'", template, path, site);
- Map params = Map.of(PARAM_KEY_SITE, site, PARAM_KEY_VERSION, nextVersion);
+ Map params = getTemplateParameters(context, path);
XsltUtils.executeTemplate(templateIs, params, getURIResolver(context), sourceIs, os);
trackChangedFiles(path);
} catch (Exception e) {
@@ -93,6 +93,18 @@ protected void executeTemplate(StudioUpgradeContext context, String path, Output
}
}
+ /**
+ * Gets the parameters to be passed to the XSLT template. By default, it includes the site name and the next version,
+ * but it can be overridden by subclasses to provide additional parameters if needed.
+ *
+ * @param context the upgrade context, which can be used to get additional information for the parameters if needed
+ * @param path the path of the file being upgraded
+ * @return a map of parameter names and values to be passed to the XSLT template
+ */
+ protected Map getTemplateParameters(StudioUpgradeContext context, String path) {
+ return Map.of(PARAM_KEY_SITE, context.getTarget(), PARAM_KEY_VERSION, nextVersion);
+ }
+
protected URIResolver getURIResolver(StudioUpgradeContext context) {
return (href, base) -> {
try {
@@ -101,7 +113,6 @@ protected URIResolver getURIResolver(StudioUpgradeContext context) {
logger.info("Failed to create a resolver for referencing documents inside XSLT forms", e);
return null;
}
-
};
}
}
diff --git a/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/BatchXsltFileUpgradeOperation.java b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/BatchXsltFileUpgradeOperation.java
index d818d05a25..70a5345b3f 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/BatchXsltFileUpgradeOperation.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/BatchXsltFileUpgradeOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,6 +16,13 @@
package org.craftercms.studio.impl.v2.upgrade.operations.site;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.craftercms.commons.upgrade.exception.UpgradeException;
+import org.craftercms.studio.api.v2.utils.StudioConfiguration;
+import org.craftercms.studio.impl.v2.upgrade.StudioUpgradeContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
@@ -23,15 +30,6 @@
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;
-import org.apache.commons.configuration2.HierarchicalConfiguration;
-import org.craftercms.commons.upgrade.exception.UpgradeException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.craftercms.studio.api.v2.utils.StudioConfiguration;
-import org.craftercms.studio.impl.v2.upgrade.StudioUpgradeContext;
-
-import javax.sql.DataSource;
-
import static org.craftercms.studio.api.v2.utils.StudioUtils.getStudioTemporaryFilesRoot;
/**
@@ -54,7 +52,7 @@ public class BatchXsltFileUpgradeOperation extends AbstractXsltFileUpgradeOperat
protected String regex;
- public BatchXsltFileUpgradeOperation(StudioConfiguration studioConfiguration, DataSource dataSource) {
+ public BatchXsltFileUpgradeOperation(StudioConfiguration studioConfiguration) {
super(studioConfiguration);
}
@@ -64,24 +62,33 @@ protected void doInit(final HierarchicalConfiguration config) {
regex = config.getString(CONFIG_KEY_REGEX);
}
+ /**
+ * Finds all files in the repository that match the regex.
+ *
+ * @param repository the repository path
+ * @return a stream of paths that match the regex
+ * @throws IOException if an error occurs while searching for files
+ */
+ public Stream getPaths(Path repository) throws IOException {
+ return Files.find(repository, Integer.MAX_VALUE,
+ (path, attrs) -> repository.relativize(path).toString().matches(regex));
+ }
+
@Override
public void doExecute(final StudioUpgradeContext context) throws UpgradeException {
var site = context.getTarget();
logger.debug("Find files that match the regex '{}' in site '{}'", regex, site);
Path repository = context.getRepositoryPath();
- try (Stream paths = Files.find(repository, Integer.MAX_VALUE,
- (path, attrs) -> repository.relativize(path).toString().matches(regex))) {
+ try (Stream paths = getPaths(repository)) {
paths.forEach(path -> {
logger.debug("Execute the XSLT template against site '{}' path '{}'", site, path);
try {
- Path temp = Files.createTempFile(getStudioTemporaryFilesRoot(), "upgrade-manager", "xslt");
+ Path temp = Files.createTempFile(getStudioTemporaryFilesRoot(), "upgrade-manager", ".xslt");
try {
OutputStream os = Files.newOutputStream(temp);
executeTemplate(context, repository.relativize(path).toString(), os);
os.close();
- if (Files.size(temp) > 0) {
- Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING);
- }
+ replaceFile(path, temp);
} finally {
Files.deleteIfExists(temp);
}
@@ -94,4 +101,17 @@ public void doExecute(final StudioUpgradeContext context) throws UpgradeExceptio
}
}
+ /**
+ * Replaces the original file with the transformed file if the transformed file is not empty.
+ *
+ * @param path the original file path
+ * @param temp the transformed file path
+ * @throws IOException if an error occurs while replacing the file
+ */
+ protected void replaceFile(Path path, Path temp) throws IOException {
+ if (Files.size(temp) > 0) {
+ Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
}
diff --git a/src/main/java/org/craftercms/studio/impl/v2/utils/Wrapper.java b/src/main/java/org/craftercms/studio/impl/v2/utils/Wrapper.java
new file mode 100644
index 0000000000..cca58946f7
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/impl/v2/utils/Wrapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.impl.v2.utils;
+
+/**
+ * A simple wrapper class to hold a value of type T. This can be used to work
+ * around the fact that Java does not to modify variables from within lambda expressions or anonymous classes.
+ *
+ * @param
+ */
+public class Wrapper {
+ private T value;
+
+ public T get() {
+ return value;
+ }
+
+ public void set(T value) {
+ this.value = value;
+ }
+
+ public boolean hasValue() {
+ return this.value != null;
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/impl/v2/utils/db/DBUtils.java b/src/main/java/org/craftercms/studio/impl/v2/utils/db/DBUtils.java
index a255343166..05eddba699 100644
--- a/src/main/java/org/craftercms/studio/impl/v2/utils/db/DBUtils.java
+++ b/src/main/java/org/craftercms/studio/impl/v2/utils/db/DBUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,6 +16,7 @@
package org.craftercms.studio.impl.v2.utils.db;
+import org.craftercms.studio.impl.v2.utils.Wrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
@@ -158,19 +159,4 @@ public interface ThrowingRunnable {
void run() throws Exception;
}
- private static class Wrapper {
- private T value;
-
- public T get() {
- return value;
- }
-
- public void set(T value) {
- this.value = value;
- }
-
- public boolean hasValue() {
- return this.value != null;
- }
- }
}
diff --git a/src/main/java/org/craftercms/studio/model/contentType/ContentType.java b/src/main/java/org/craftercms/studio/model/contentType/ContentType.java
new file mode 100644
index 0000000000..d35c794488
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/contentType/ContentType.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.contentType;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.craftercms.studio.api.v2.dal.security.NormalizedRole;
+import org.craftercms.studio.api.v2.utils.StudioUtils;
+
+import java.util.*;
+
+import static java.util.Collections.emptyList;
+import static org.apache.commons.collections4.CollectionUtils.emptyIfNull;
+
+/**
+ * Represents a content type in the system.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ContentType {
+ @JsonAlias("content-type")
+ @JsonProperty("id")
+ protected String id;
+ @JsonAlias("title")
+ @JsonProperty("label")
+ protected String label;
+ @JsonAlias("allowed-roles")
+ @JsonProperty("allowedRoles")
+ protected Collection allowedRoles = emptyList();
+ @JsonAlias("delete-dependencies")
+ @JsonProperty("deleteDependencies")
+ protected List deleteDependencies = emptyList();
+ @JsonAlias("copy-dependencies")
+ @JsonProperty("copyDependencies")
+ protected List copyDependencies = emptyList();
+ protected boolean previewable;
+ protected String imageThumbnail;
+ protected boolean noThumbnail;
+ // Because both excludes and excludes need to be set together, we use a single property for both, and ignore it during serialization
+ // so the JSON representation still has the same structure as before, with separate includes and excludes properties
+ @JsonProperty(value = "paths", access = JsonProperty.Access.WRITE_ONLY)
+ protected PathIncludeExcludes pathIncludeExcludes;
+ protected boolean quickCreate;
+ protected String quickCreatePath;
+
+ public Collection getAllowedRoles() {
+ return allowedRoles;
+ }
+
+ public void setAllowedRoles(Collection allowedRoles) {
+ Set normalizedRoles = new HashSet<>();
+ for (String role : emptyIfNull(allowedRoles)) {
+ normalizedRoles.add(new NormalizedRole(role));
+ }
+ this.allowedRoles = normalizedRoles;
+ }
+
+ public List getCopyDependencies() {
+ return copyDependencies;
+ }
+
+ public void setCopyDependencies(List copyDependencies) {
+ this.copyDependencies = copyDependencies;
+ }
+
+ public List getDeleteDependencies() {
+ return deleteDependencies;
+ }
+
+ public void setDeleteDependencies(List deleteDependencies) {
+ this.deleteDependencies = deleteDependencies;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getImageThumbnail() {
+ return imageThumbnail;
+ }
+
+ public void setImageThumbnail(String imageThumbnail) {
+ this.imageThumbnail = imageThumbnail;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public boolean isNoThumbnail() {
+ return noThumbnail;
+ }
+
+ public void setNoThumbnail(boolean noThumbnail) {
+ this.noThumbnail = noThumbnail;
+ }
+
+ public Collection getPathIncludes() {
+ return emptyIfNull(pathIncludeExcludes.includes()).stream()
+ .map(PathPattern::getPattern)
+ .toList();
+ }
+
+ public Collection getPathExcludes() {
+ return emptyIfNull(pathIncludeExcludes.excludes()).stream()
+ .map(PathPattern::getPattern)
+ .toList();
+ }
+
+ public void setPathIncludeExcludes(PathIncludeExcludes pathIncludeExcludes) {
+ this.pathIncludeExcludes = pathIncludeExcludes;
+ }
+
+ public boolean isPreviewable() {
+ return previewable;
+ }
+
+ public void setPreviewable(boolean previewable) {
+ this.previewable = previewable;
+ }
+
+ public boolean isQuickCreate() {
+ return quickCreate;
+ }
+
+ public void setQuickCreate(boolean quickCreate) {
+ this.quickCreate = quickCreate;
+ }
+
+ public String getQuickCreatePath() {
+ return quickCreatePath;
+ }
+
+ public void setQuickCreatePath(String quickCreatePath) {
+ this.quickCreatePath = quickCreatePath;
+ }
+
+ public Type getType() {
+ return StudioUtils.getContentTypeTypeById(id);
+ }
+
+ public enum Type {
+ page, component, unknown
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/model/contentType/CopyDependency.java b/src/main/java/org/craftercms/studio/model/contentType/CopyDependency.java
new file mode 100644
index 0000000000..e29b6cd393
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/contentType/CopyDependency.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.contentType;
+
+/**
+ * Represents a copy dependency for a content type.
+ *
+ * @param pattern the pattern to match the dependencies to be copied
+ * @param target the target location where the dependencies should be copied to
+ */
+public record CopyDependency(String pattern,
+ String target) {
+}
diff --git a/src/main/java/org/craftercms/studio/model/contentType/DeleteDependency.java b/src/main/java/org/craftercms/studio/model/contentType/DeleteDependency.java
new file mode 100644
index 0000000000..8e3b002497
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/contentType/DeleteDependency.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.contentType;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents a delete dependency for a content type.
+ *
+ * @param pattern the pattern to match the dependencies to be deleted
+ * @param removeEmptyFolder a flag indicating whether to remove empty folders after deletion
+ */
+public record DeleteDependency(String pattern,
+ @JsonAlias("remove-empty-folder") boolean removeEmptyFolder) {
+}
diff --git a/src/main/java/org/craftercms/studio/model/contentType/PathIncludeExcludes.java b/src/main/java/org/craftercms/studio/model/contentType/PathIncludeExcludes.java
new file mode 100644
index 0000000000..f0d245ce0e
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/contentType/PathIncludeExcludes.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.contentType;
+
+import java.util.Collection;
+
+/**
+ * Represents the includes and excludes for content type paths.
+ *
+ * @param includes the included path patterns for the content type
+ * @param excludes the excluded path patterns for the content type
+ */
+public record PathIncludeExcludes(Collection includes,
+ Collection excludes) {
+}
diff --git a/src/main/java/org/craftercms/studio/model/contentType/PathPattern.java b/src/main/java/org/craftercms/studio/model/contentType/PathPattern.java
new file mode 100644
index 0000000000..a0889f9d7d
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/contentType/PathPattern.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.contentType;
+
+/**
+ * Represents a pattern for content type paths includes and excludes.
+ * This is a class and not a record to avoid issues with Jackson deserialization when used in
+ * a Collection. See {@link PathIncludeExcludes}
+ */
+public class PathPattern {
+ private final String pattern;
+
+ public PathPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+}
diff --git a/src/main/java/org/craftercms/studio/model/rest/ApiResponse.java b/src/main/java/org/craftercms/studio/model/rest/ApiResponse.java
index eca1259be9..eca935a6fb 100644
--- a/src/main/java/org/craftercms/studio/model/rest/ApiResponse.java
+++ b/src/main/java/org/craftercms/studio/model/rest/ApiResponse.java
@@ -208,9 +208,14 @@ public class ApiResponse {
public static final ApiResponse LOGGER_NOT_FOUND = new ApiResponse(53000, "The logger was not found",
"Check if you sent in the right logger name or " +
"add 'createIfAbsent=true' parameter to create the logger if it does not exist", StringUtils.EMPTY);
+ // 54000 - 55000
public static final ApiResponse CONFIGURATION_PROFILE_NOT_FOUND = new ApiResponse(54000, "The profile was not found",
"Check if you sent in the right profileId name", StringUtils.EMPTY);
+ // 55000 - 56000
+ public static final ApiResponse CONTENT_TYPE_IN_USE = new ApiResponse(55000, "The content type cannot be deleted because it is still in use by content items",
+ "Check if you sent in the right content type id or add 'deleteDependencies' to force delete", StringUtils.EMPTY);
+
private int code;
private String message;
private String remedialAction;
diff --git a/src/main/java/org/craftercms/studio/model/rest/contentType/DeleteContentTypeRequest.java b/src/main/java/org/craftercms/studio/model/rest/contentType/DeleteContentTypeRequest.java
new file mode 100644
index 0000000000..b66b1537d8
--- /dev/null
+++ b/src/main/java/org/craftercms/studio/model/rest/contentType/DeleteContentTypeRequest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.model.rest.contentType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import org.craftercms.commons.validation.annotations.param.ValidConfigurationPath;
+
+/**
+ * Request for deleting a content-type.
+ */
+@JsonIgnoreProperties
+public class DeleteContentTypeRequest {
+ @ValidConfigurationPath
+ protected String contentType;
+
+ protected boolean deleteDependencies;
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public boolean isDeleteDependencies() {
+ return deleteDependencies;
+ }
+
+ public void setDeleteDependencies(boolean deleteDependencies) {
+ this.deleteDependencies = deleteDependencies;
+ }
+
+}
diff --git a/src/main/resources/crafter/studio/extension/rendering-overlay-context.xml b/src/main/resources/crafter/studio/extension/rendering-overlay-context.xml
index ab3343cc3f..0b60cce11e 100644
--- a/src/main/resources/crafter/studio/extension/rendering-overlay-context.xml
+++ b/src/main/resources/crafter/studio/extension/rendering-overlay-context.xml
@@ -1,6 +1,6 @@
diff --git a/src/main/resources/crafter/studio/studio-upgrade-context.xml b/src/main/resources/crafter/studio/studio-upgrade-context.xml
index 54ea367b67..1c1e4f9570 100644
--- a/src/main/resources/crafter/studio/studio-upgrade-context.xml
+++ b/src/main/resources/crafter/studio/studio-upgrade-context.xml
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/crafter/studio/upgrade/pipelines.yaml b/src/main/resources/crafter/studio/upgrade/pipelines.yaml
index 0164c34900..5fa588ad08 100644
--- a/src/main/resources/crafter/studio/upgrade/pipelines.yaml
+++ b/src/main/resources/crafter/studio/upgrade/pipelines.yaml
@@ -763,6 +763,13 @@ pipelines:
- pattern: "\\._toQuery\\(\\)"
replacement: '.toQuery()'
commitDetails: Upgrade to OpenSearch 3 in site scripts
+ - currentVersion: 5.0.0.1
+ nextVersion: 5.0.0.2
+ operations:
+ - type: contentTypeConfigMergeUpgrader
+ regex: config/studio/content-types/.+/form-definition\.xml
+ template: crafter/studio/upgrade/5.0.x/content-type/content-type-merge-v5.0.0.2.xslt
+ commitDetails: Merge content type config.xml into form-definition.xml and remove config.xml files
# Pipeline to upgrade blueprints
blueprint:
diff --git a/src/main/webapp/default-site/scripts/api/ContentServices.groovy b/src/main/webapp/default-site/scripts/api/ContentServices.groovy
index ff6c276506..b69caf3238 100644
--- a/src/main/webapp/default-site/scripts/api/ContentServices.groovy
+++ b/src/main/webapp/default-site/scripts/api/ContentServices.groovy
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -66,11 +66,6 @@ class ContentServices {
return contentServicesImpl.getContentVersionAtPath(site, path, version)
}
- static getContentType(context, site, type) {
- def contentTypeServicesImpl = ServiceFactory.getContentTypeServices(context);
- return contentTypeServicesImpl.getContentType(site, type)
- }
-
static reorderItems(context, site, path, before, after) {
def contentServicesImpl = ServiceFactory.getContentServices(context);
return contentServicesImpl.reorderItems(site, path, before, after);
diff --git a/src/main/webapp/default-site/scripts/api/ContentTypeServices.groovy b/src/main/webapp/default-site/scripts/api/ContentTypeServices.groovy
deleted file mode 100644
index 2efffde3e3..0000000000
--- a/src/main/webapp/default-site/scripts/api/ContentTypeServices.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package scripts.api
-/**
- * content type services
- */
-class ContentTypeServices {
-
- /**
- * create the context object
- * @param applicationContext - studio application's contect (spring container etc)
- * @param request - web request if in web request context
- */
- static createContext(applicationContext, request) {
- return ServiceFactory.createContext(applicationContext, request)
- }
-
- /**
- * get all content types for a given site
- * @param site - the project ID
- * @param searchable - include non-searchable types (true/false)
- */
- static getContentTypes(context, site, searchable) {
- def contentTypeServiceImpl = ServiceFactory.getContentTypeServices(context)
- return contentTypeServiceImpl.getContentTypes(site, searchable)
- }
-
- /**
- * get allowed content types for a given path
- * @param site - the project ID
- * @param path - the path
- */
- static getAllowedContentTypesForPath(context, site, path) {
- def contentTypeServiceImpl = ServiceFactory.getContentTypeServices(context)
- return contentTypeServiceImpl.getAllowedContentTypesForPath(site, path)
- }
-
- /**
- * Get a content type definition
- * @param site - the Project ID
- * @param type - the content type
- */
- def getContentType(site, type) {
-
- }
-}
diff --git a/src/main/webapp/default-site/scripts/api/ServiceFactory.groovy b/src/main/webapp/default-site/scripts/api/ServiceFactory.groovy
index 266ff280a3..c16d36750b 100644
--- a/src/main/webapp/default-site/scripts/api/ServiceFactory.groovy
+++ b/src/main/webapp/default-site/scripts/api/ServiceFactory.groovy
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,13 +16,12 @@
package scripts.api
-import scripts.libs.Cookies
import scripts.api.impl.content.SpringContentServices
-import scripts.api.impl.content.SpringContentTypeServices
import scripts.api.impl.content.SpringPageNavigationOrderServices
import scripts.api.impl.security.SpringSecurityServices
import scripts.api.impl.site.SpringSiteServices
import scripts.api.impl.user.SpringUserServices
+import scripts.libs.Cookies
/**
* Class is a factory used by the API wrappers to find their implementation
@@ -53,13 +52,6 @@ class ServiceFactory {
return new SpringContentServices(context)
}
- /**
- * return the implementation for content type services
- */
- static getContentTypeServices(context) {
- return new SpringContentTypeServices(context)
- }
-
/**
* return the implementation for security services
*
diff --git a/src/main/webapp/default-site/scripts/api/impl/content/SpringContentTypeServices.groovy b/src/main/webapp/default-site/scripts/api/impl/content/SpringContentTypeServices.groovy
deleted file mode 100644
index 85dc3e4581..0000000000
--- a/src/main/webapp/default-site/scripts/api/impl/content/SpringContentTypeServices.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-/**
- * @author Dejan Brkic
- */
-package scripts.api.impl.content;
-
-/**
- * content type services
- */
-class SpringContentTypeServices {
-
- static CONTENT_TYPE_SERVICES_BEAN = "cstudioContentTypeService"
-
- def context = null
-
- def SpringContentTypeServices(context) {
- this.context = context
- }
-
- def getContentTypes(site, searchable) {
- def springBackedService = this.context.applicationContext.get(CONTENT_TYPE_SERVICES_BEAN)
- return springBackedService.getAllContentTypes(site, searchable)
- }
-
- def getContentType(site, type) {
- def springBackedService = this.context.applicationContext.get(CONTENT_TYPE_SERVICES_BEAN)
- return springBackedService.getContentType(site, type)
- }
-
- def getAllowedContentTypesForPath(site, path) {
- def springBackedService = this.context.applicationContext.get(CONTENT_TYPE_SERVICES_BEAN)
- return springBackedService.getAllowedContentTypesForPath(site, path);
- }
-}
diff --git a/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-type.get.groovy b/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-type.get.groovy
deleted file mode 100644
index 5376294cdb..0000000000
--- a/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-type.get.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-/**
- * @author Dejan Brkic
- */
-
-import org.apache.commons.lang3.StringUtils
-import scripts.api.ContentServices
-
-def result = [:]
-def site = params.site_id
-def type = params.type
-
-/** Validate Parameters */
-def invalidParams = false
-def paramsList = []
-
-// site_id
-try {
- if (StringUtils.isEmpty(site)) {
- site = params.site
- if (StringUtils.isEmpty(site)) {
- invalidParams = true
- paramsList.add("site_id")
- }
- }
-} catch (Exception e) {
- invalidParams = true
- paramsList.add("site_id")
-}
-
-if (invalidParams) {
- response.setStatus(400)
- result.message = "Invalid parameter(s): " + paramsList
-} else {
- def context = ContentServices.createContext(applicationContext, request)
-
- result = ContentServices.getContentType(context, site, type)
-
-}
-return result
diff --git a/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-types.get.groovy b/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-types.get.groovy
deleted file mode 100644
index b970ee355b..0000000000
--- a/src/main/webapp/default-site/scripts/rest/api/1/content/get-content-types.get.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-/**
- * @author Dejan Brkic
- */
-
-import org.apache.commons.lang3.StringUtils
-import scripts.api.ContentTypeServices
-
-def result = [:]
-def site = params.site
-def path = params.path
-
-/** Validate Parameters */
-def invalidParams = false
-def paramsList = []
-
-// site_id
-try {
- if (StringUtils.isEmpty(site)) {
- site = params.site
- if (StringUtils.isEmpty(site)) {
- invalidParams = true
- paramsList.add("site_id")
- }
- }
-} catch (Exception e) {
- invalidParams = true
- paramsList.add("site_id")
-}
-
-if (invalidParams) {
- response.setStatus(400)
- result.message = "Invalid parameter(s): " + paramsList
-} else {
- def context = ContentTypeServices.createContext(applicationContext, request)
- if (path != null) {
- result = ContentTypeServices.getAllowedContentTypesForPath(context, site, path)
- } else {
- result = ContentTypeServices.getContentTypes(context, site, true)
- }
-
-}
-return result
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/config.xml
deleted file mode 100644
index 4f78e08624..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Articles Widget
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- component-articles-widget.png
-
-
- ^/site/components/articles-widget/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/form-definition.xml
index a2f6e746e2..f2015e3ae5 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/articles-widget/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/config.xml
deleted file mode 100644
index 1222a096ac..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Contact Widget
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- component-contact-widget.png
-
-
- ^/site/components/contacts/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/form-definition.xml
index f2ae4c2f05..27c09eff07 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/contact-widget/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/config.xml
deleted file mode 100644
index 06d4c828ae..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Feature
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- component-feature.png
-
-
- ^/site/components/features/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/form-definition.xml
index 65a4e86c00..e6b48a8d05 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/feature/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/config.xml
deleted file mode 100644
index 32aba74823..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Header
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- component-header.png
-
-
- ^/site/components/headers/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/form-definition.xml
index 639fe2b8a4..4ca3a4e8f6 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/header/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/config.xml
deleted file mode 100644
index 26e2b88680..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Left Rail
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- component-left-rail.png
-
-
- ^/site/components/left-rails/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/form-definition.xml
index 7dc6385aba..d6baf18adb 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/left-rail/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/config.xml
deleted file mode 100644
index 25272dc5a7..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Section Defaults
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- section-defaults.png
-
-
- ^/site/website/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/form-definition.xml
index 1d3a984097..2a84d11a57 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/level-descriptor/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/config.xml
deleted file mode 100755
index 43057711c3..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/config.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
- Social Media Widget
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- ^/site/components/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/form-definition.xml
old mode 100755
new mode 100644
index 9cce9e6ce1..e82873ddcf
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/component/social-media-widget/form-definition.xml
@@ -1,525 +1,532 @@
+
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/config.xml
deleted file mode 100644
index 86ae812b09..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Article
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- true
- true
- true
- /site/website/articles/{year}/{month}
- false
- page-article.png
-
-
- ^/site/website/articles/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/form-definition.xml
index bd4a56bad0..9d88054577 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/article/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/config.xml
deleted file mode 100644
index 876738af4e..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Category Landing
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- true
- true
- false
-
- false
- page-category-landing.png
-
-
- ^/site/website/(?!articles/)(.*)
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/form-definition.xml
index 93d27bc620..b25ea2b6e5 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/category-landing/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/config.xml
deleted file mode 100644
index 13865b19ed..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Home
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- true
- true
- false
-
- false
- page-home.png
-
-
- ^/site/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/form-definition.xml
index 4d121ee4ae..c0096586d4 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/home/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/config.xml
deleted file mode 100644
index cd3787e8b8..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Search Results
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- true
- true
- false
-
- false
- page-search-results.png
-
-
- ^/site/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/form-definition.xml
index dd0471926d..5b5fa81488 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/page/search-results/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/config.xml
deleted file mode 100644
index 33891f41f1..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Taxonomy
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- false
- taxonomy.png
-
-
- ^/site/taxonomy/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/form-definition.xml
index 249d4deb59..ae074f92d2 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/content-types/taxonomy/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/studio_version.xml b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/studio_version.xml
index 1fb428280a..1adc2652d0 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/studio_version.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/1000_website_editorial/config/studio/studio_version.xml
@@ -15,5 +15,5 @@
~ along with this program. If not, see .
-->
- 5.0.0.1
+ 5.0.0.2
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/config.xml
deleted file mode 100644
index 461a53be7e..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Item: Company
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/items/companies/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/form-definition.xml
index f98492da9c..45ad13939b 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/company/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/config.xml
deleted file mode 100644
index 9812cb9bc9..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Section Defaults
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- ^/site/website/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/form-definition.xml
index 76bca6ecfe..31adc30c18 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/level-descriptor/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/config.xml
deleted file mode 100644
index 5b347b45ea..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Item: Product
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/items/products/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/form-definition.xml
index 0ec373f573..fd38469e39 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/component/product/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/config.xml
deleted file mode 100644
index 6d735d4093..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Entry
-
- simple
- not-used
- xml
- true
- true
- false
-
- true
-
-
-
- /site/website/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/form-definition.xml
index 85d80f8ee6..3216c502e9 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/page/catalog/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/config.xml
deleted file mode 100644
index 3dd3c26aa5..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Taxonomy
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/taxonomy/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/form-definition.xml
index 284d71d701..2d503ffd82 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/content-types/taxonomy/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/studio_version.xml b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/studio_version.xml
index 1fb428280a..1adc2652d0 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/studio_version.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/2000_headless_store/config/studio/studio_version.xml
@@ -15,5 +15,5 @@
~ along with this program. If not, see .
-->
- 5.0.0.1
+ 5.0.0.2
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/config.xml
deleted file mode 100644
index 0f11a557a8..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/config.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- Section Defaults
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/form-definition.xml
index 891daae2d6..7a6682938c 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/component/level-descriptor/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/config.xml
deleted file mode 100644
index f5e3b8753f..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/config.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- Entry
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- true
- true
- false
-
- true
- image.jpg
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/form-definition.xml
index 413e6eed83..70f6922318 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/content-types/page/entry/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/studio_version.xml b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/studio_version.xml
index 1fb428280a..1adc2652d0 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/studio_version.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/4000_empty/config/studio/studio_version.xml
@@ -15,5 +15,5 @@
~ along with this program. If not, see .
-->
- 5.0.0.1
+ 5.0.0.2
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/config.xml
deleted file mode 100644
index 38d021ffd4..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Item: Author
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/items/authors/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/form-definition.xml
index 951656dcb9..d376421b4c 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/author/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/config.xml
deleted file mode 100644
index 9812cb9bc9..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Section Defaults
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- ^/site/website/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/form-definition.xml
index 76bca6ecfe..31adc30c18 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/level-descriptor/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/config.xml
deleted file mode 100644
index de4729acbb..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Item: Post
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/items/posts/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/form-definition.xml
index 7e5c731e1c..34292386ab 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/component/post/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/config.xml
deleted file mode 100644
index 337b9784ec..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Page: Item Explorer
-
- simple
- not-used
- xml
- true
- true
- false
-
- true
-
-
-
- /site/website/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/form-definition.xml
index ecce4b7599..ac9cfe6eba 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/page/item-explorer/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/config.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/config.xml
deleted file mode 100644
index 3dd3c26aa5..0000000000
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/config.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Taxonomy
-
- simple
- NOT-USED-BY-SIMPLE-FORM-ENGINE
- xml
- false
- false
- false
-
- true
-
-
-
- /site/taxonomy/.*
-
-
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/form-definition.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/form-definition.xml
index 284d71d701..6f83b41ea5 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/form-definition.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/content-types/taxonomy/form-definition.xml
@@ -1,5 +1,6 @@
+
-
diff --git a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/studio_version.xml b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/studio_version.xml
index 1fb428280a..1adc2652d0 100644
--- a/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/studio_version.xml
+++ b/src/main/webapp/repo-bootstrap/global/blueprints/5000_headless_blog/config/studio/studio_version.xml
@@ -15,5 +15,5 @@
~ along with this program. If not, see .
-->
- 5.0.0.1
+ 5.0.0.2
diff --git a/src/test/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImplTest.java b/src/test/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImplTest.java
index 7c70a06045..f961775344 100644
--- a/src/test/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImplTest.java
+++ b/src/test/java/org/craftercms/studio/impl/v2/service/content/internal/ContentTypeServiceInternalImplTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -16,12 +16,14 @@
package org.craftercms.studio.impl.v2.service.content.internal;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.studio.api.v1.exception.ContentNotFoundException;
import org.craftercms.studio.api.v1.exception.ServiceLayerException;
import org.craftercms.studio.api.v1.service.site.SiteService;
import org.craftercms.studio.api.v2.service.config.ConfigurationService;
import org.craftercms.studio.api.v2.service.content.ContentService;
+import org.craftercms.studio.model.contentType.ContentType;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
@@ -31,9 +33,13 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.util.ReflectionTestUtils;
+import java.io.IOException;
+import java.io.InputStream;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@@ -138,4 +144,19 @@ public void getFormControllerReturnResource() throws ServiceLayerException {
assertEquals(resultPair.getKey(), CONTENT_TYPE_WITH_FORM_CONTROLLER_FULL_FORM_CONTROLLER_PATH);
assertEquals(resultPair.getValue(), resource);
}
+
+ @Test
+ public void getContentTypeTest() throws ServiceLayerException, IOException {
+ InputStream inputStream = new ClassPathResource("crafter/studio/content-type/" + CONTENT_TYPE + "/form-definition.xml").getInputStream();
+ when(contentService.getContent(SITE_ID, CONTENT_TYPE_DEFINITION_PATH)).thenReturn(inputStream);
+ ContentType contentType = service.loadContentType(SITE_ID, CONTENT_TYPE);
+
+ String expectedJson = "{\"previewable\":true,\"imageThumbnail\":\"page-test1.png\",\"noThumbnail\":false,\"quickCreate\":true," +
+ "\"quickCreatePath\":\"/site/website/tests/{year}/{month}\",\"type\":\"unknown\",\"pathExcludes\":[\"^/site/website/tests/excluded.*\"," +
+ "\"^/site/website/tests/excluded2.*\"],\"pathIncludes\":[\"^/site/website/tests/.*\"],\"id\":\"myContentType\"," +
+ "\"label\":\"Test Content Type\",\"allowedRoles\":[{\"name\":\"author\"},{\"name\":\"admin\"}]," +
+ "\"deleteDependencies\":[{\"pattern\":\"^/site/website/articles/.*\",\"removeEmptyFolder\":true}]," +
+ "\"copyDependencies\":[{\"pattern\":\"^/site/website/articles/.*\",\"target\":\"/site/website/articles2\"}]}";
+ assertEquals(expectedJson, new ObjectMapper().writeValueAsString(contentType));
+ }
}
diff --git a/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgraderTest.java b/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgraderTest.java
new file mode 100644
index 0000000000..bd21f8d4b1
--- /dev/null
+++ b/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/contentType/ContentTypeConfigMergeUpgraderTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.craftercms.studio.impl.v2.upgrade.operations.contentType;
+
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.io.IOUtils;
+import org.craftercms.studio.api.v2.utils.StudioConfiguration;
+import org.craftercms.studio.impl.v2.upgrade.StudioUpgradeContext;
+import org.craftercms.studio.impl.v2.upgrade.operations.site.AbstractXsltFileUpgradeOperation;
+import org.craftercms.studio.impl.v2.upgrade.operations.site.BatchXsltFileUpgradeOperation;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xmlunit.builder.DiffBuilder;
+import org.xmlunit.diff.Diff;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_FILE_NAME;
+import static org.craftercms.studio.api.v2.utils.StudioUtils.createTempFile;
+import static org.craftercms.studio.api.v2.utils.StudioUtils.getStudioTemporaryFilesRoot;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ContentTypeConfigMergeUpgraderTest {
+ private static final Logger logger = LoggerFactory.getLogger(ContentTypeConfigMergeUpgraderTest.class);
+
+ @Mock
+ private StudioConfiguration studioConfiguration;
+ @Spy
+ @InjectMocks
+ private ContentTypeConfigMergeUpgrader upgrader;
+
+ private String configFileName;
+
+ @Before
+ public void setUp() throws Exception {
+ configFileName = UUID.randomUUID() + "config.xml";
+ var config = mock(HierarchicalConfiguration.class);
+ when(config.getString(BatchXsltFileUpgradeOperation.CONFIG_KEY_REGEX)).thenReturn("config/studio/content-types/.+/form-definition\\.xml");
+ when(config.getString(AbstractXsltFileUpgradeOperation.CONFIG_KEY_TEMPLATE)).thenReturn("crafter/studio/upgrade/5.0.x/content-type/content-type-merge-v5.0.0.2.xslt");
+ when(studioConfiguration.getProperty(CONFIGURATION_SITE_CONTENT_TYPES_CONFIG_FILE_NAME)).thenReturn(configFileName);
+ upgrader.init("1", "2", config);
+ }
+
+ @Test
+ public void testUpgrade() throws Exception {
+ String basePath = "src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/";
+ File originalFormFile = new File(basePath + "form-definition.xml");
+ File originalConfigFile = new File(basePath + "config.xml");
+ Path configFilePath = getStudioTemporaryFilesRoot().resolve(configFileName);
+ Path formFilePath = createTempFile("form-definition.xml");
+ formFilePath.toFile().deleteOnExit();
+ configFilePath.toFile().deleteOnExit();
+ Files.copy(originalFormFile.toPath(), formFilePath, StandardCopyOption.REPLACE_EXISTING);
+
+ Files.copy(originalConfigFile.toPath(), configFilePath, StandardCopyOption.REPLACE_EXISTING);
+
+ File resultFile = formFilePath.toFile();
+ File expectedFile = new File(basePath + "expected.xml");
+
+ StudioUpgradeContext context = mock(StudioUpgradeContext.class);
+ when(context.getTarget()).thenReturn("test-site");
+ when(context.getRepositoryPath()).thenReturn(Path.of(getStudioTemporaryFilesRoot().toString()));
+ when(context.getFile(getStudioTemporaryFilesRoot().relativize(formFilePath).toString())).thenReturn(formFilePath);
+
+ (doReturn(Stream.of(formFilePath)).when(upgrader)).getPaths(any());
+
+ upgrader.doExecute(context);
+
+ // Assert
+ try (InputStream expectedIn = new FileInputStream(expectedFile);
+ InputStream actualIn = new FileInputStream(resultFile)) {
+ String expectedXml = IOUtils.toString(expectedIn, StandardCharsets.UTF_8);
+ String actualXml = IOUtils.toString(actualIn, StandardCharsets.UTF_8);
+
+ // Compare the result
+ Diff diff = DiffBuilder
+ .compare(expectedXml)
+ .withTest(actualXml)
+ .ignoreWhitespace()
+ .ignoreComments()
+ .checkForSimilar()
+ .build();
+
+ if (diff.hasDifferences()) {
+ logger.debug(actualXml);
+ }
+
+ // there should not be any differences
+ assertEquals(0, IterableUtils.size(diff.getDifferences()),
+ "The result XML should be equal to the expected XML");
+ }
+ }
+}
diff --git a/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/site/ContentTypeControllerUpgradeOperationTest.java b/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/site/ContentTypeControllerUpgradeOperationTest.java
index f4303cbbb2..5bdd8b2100 100644
--- a/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/site/ContentTypeControllerUpgradeOperationTest.java
+++ b/src/test/java/org/craftercms/studio/impl/v2/upgrade/operations/site/ContentTypeControllerUpgradeOperationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
+ * Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
@@ -64,10 +64,10 @@ public void testDefaultScript() throws IOException, UpgradeException {
@Test
public void testCustomScript() throws UpgradeException, IOException, URISyntaxException {
- ClassPathResource inputResource = new ClassPathResource("crafter/studio/upgrade/content-type/custom/input.groovy");
+ ClassPathResource inputResource = new ClassPathResource("crafter/studio/upgrade/content-type/5.0/5.0.0/custom/input.groovy");
Path inputScriptFile = createTempFile("controller.groovy", inputResource.getInputStream());
operation.updateFile(mock(StudioUpgradeContext.class), inputScriptFile);
- ClassPathResource expectedResource = new ClassPathResource("crafter/studio/upgrade/content-type/custom/expected.groovy");
+ ClassPathResource expectedResource = new ClassPathResource("crafter/studio/upgrade/content-type/5.0/5.0.0/custom/expected.groovy");
Assert.assertEquals("Output file does not match ", -1, Files.mismatch(inputScriptFile, Paths.get(expectedResource.getURL().toURI())));
}
diff --git a/src/test/resources/crafter/studio/content-type/myContentType/form-definition.xml b/src/test/resources/crafter/studio/content-type/myContentType/form-definition.xml
new file mode 100644
index 0000000000..95174f4ef1
--- /dev/null
+++ b/src/test/resources/crafter/studio/content-type/myContentType/form-definition.xml
@@ -0,0 +1,905 @@
+
+
+
diff --git a/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/config.xml b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/config.xml
new file mode 100644
index 0000000000..713d23629f
--- /dev/null
+++ b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/config.xml
@@ -0,0 +1,50 @@
+
+
+
+ Article
+
+ simple
+ NOT-USED-BY-SIMPLE-FORM-ENGINE
+ xml
+ true
+ true
+ true
+ /site/website/articles/{year}/{month}
+ false
+ page-article.png
+
+
+ ^/site/website/articles/.*
+
+
+
+
+ ^/site/website/articles/.*
+ true
+
+
+
+
+ ^/site/website/articles/.*
+ /site/website/articles2
+
+
+
+ author
+ admin
+
+
diff --git a/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/expected.xml b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/expected.xml
new file mode 100644
index 0000000000..56ef1aeccc
--- /dev/null
+++ b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/expected.xml
@@ -0,0 +1,902 @@
+
+
+
diff --git a/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/form-definition.xml b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/form-definition.xml
new file mode 100644
index 0000000000..706cfddc8a
--- /dev/null
+++ b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0.2/form-definition.xml
@@ -0,0 +1,879 @@
+
+
+
diff --git a/src/test/resources/crafter/studio/upgrade/content-type/custom/expected.groovy b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0/custom/expected.groovy
similarity index 100%
rename from src/test/resources/crafter/studio/upgrade/content-type/custom/expected.groovy
rename to src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0/custom/expected.groovy
diff --git a/src/test/resources/crafter/studio/upgrade/content-type/custom/input.groovy b/src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0/custom/input.groovy
similarity index 100%
rename from src/test/resources/crafter/studio/upgrade/content-type/custom/input.groovy
rename to src/test/resources/crafter/studio/upgrade/content-type/5.0/5.0.0/custom/input.groovy