diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/CompanyLocaleForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/CompanyLocaleForm.java new file mode 100644 index 000000000000..784b607ed075 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/CompanyLocaleForm.java @@ -0,0 +1,39 @@ +package com.dotcms.rest.api.v1.system; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Form for saving company locale information (language and timezone). + * + * @author hassandotcms + */ +public class CompanyLocaleForm { + + private final String languageId; + private final String timeZoneId; + + @JsonCreator + public CompanyLocaleForm( + @JsonProperty("languageId") final String languageId, + @JsonProperty("timeZoneId") final String timeZoneId) { + this.languageId = languageId; + this.timeZoneId = timeZoneId; + } + + public String getLanguageId() { + return languageId; + } + + public String getTimeZoneId() { + return timeZoneId; + } + + @Override + public String toString() { + return "CompanyLocaleForm{" + + "languageId='" + languageId + '\'' + + ", timeZoneId='" + timeZoneId + '\'' + + '}'; + } +} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationHelper.java index b35147c981a5..14a284a33cad 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationHelper.java @@ -8,11 +8,14 @@ import com.dotcms.enterprise.license.LicenseLevel; import com.dotcms.enterprise.license.LicenseManager; import com.dotmarketing.business.APILocator; +import com.dotmarketing.exception.InvalidTimeZoneException; import com.dotmarketing.util.Config; import com.dotmarketing.util.Constants; import com.dotmarketing.util.Logger; import com.dotmarketing.util.Mailer; import com.dotmarketing.util.UtilMethods; +import com.liferay.portal.auth.PrincipalThreadLocal; +import com.liferay.portal.ejb.CompanyManagerUtil; import com.liferay.portal.language.LanguageException; import com.liferay.portal.language.LanguageUtil; import com.liferay.portal.model.User; @@ -341,6 +344,32 @@ private void systemNotifyTestEmail(final User user, final String email, systemMessageEventUtil.pushSimpleTextEvent(message); } + /** + * Saves company locale information (language and timezone) for the default company user. + * This validates the timezone, updates the default user's locale in the database, + * sets the JVM-wide default timezone, and flushes the user cache. + * + * @param languageId the Java locale string (e.g. "en_US") + * @param timeZoneId the Java TimeZone ID (e.g. "America/New_York") + * @param user the admin user performing the operation + * @throws InvalidTimeZoneException if the timezone is invalid or incompatible with the database + */ + public void saveCompanyLocaleInfo(final String languageId, final String timeZoneId, + final User user) throws InvalidTimeZoneException { + + try { + PrincipalThreadLocal.setName(user.getUserId()); + CompanyManagerUtil.updateUsers(languageId, timeZoneId, null, false, false, null); + } catch (InvalidTimeZoneException e) { + throw e; + } catch (Exception e) { + Logger.error(this, "Error saving locale information for current company: " + e.getMessage(), e); + throw new RuntimeException("Error saving locale information", e); + } finally { + PrincipalThreadLocal.setName(null); + } + } + //This regex should be able to capture anything like this: dotCMS Website //Where (dotCMS Website) is optional as well as the use of <..> public static final String SENDER_NAME_EMAIL_REGEX = "((\\s*?)(\\w*?)(\\s*?))*?(\\<*[a-zA-Z0-9._-]+\\@[a-zA-Z0-9._-]+\\>*)$"; diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java index b5d2ddc4e13a..9ff729323967 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java @@ -4,11 +4,21 @@ import com.dotcms.featureflag.FeatureFlagName; import com.dotcms.rest.InitDataObject; +import com.dotcms.rest.ResponseEntityStringView; import com.dotcms.rest.WebResource.InitBuilder; import com.dotcms.rest.api.v1.maintenance.JVMInfoResource; +import com.dotmarketing.exception.InvalidTimeZoneException; +import com.dotmarketing.util.Logger; import com.dotmarketing.util.StringUtils; +import com.dotmarketing.util.UtilMethods; import com.google.common.collect.ImmutableSet; import com.liferay.util.StringPool; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.vavr.Tuple2; import java.io.IOException; @@ -224,4 +234,68 @@ public Response validateEmail( return Response.ok(new ResponseEntityView(OK)).build(); } + /** + * Saves company locale information (language and timezone). + * Updates the default company user's locale, sets the JVM-wide default timezone, + * and flushes the user cache. + * + * @param request the HTTP request + * @param response the HTTP response + * @param form the locale form containing languageId and timeZoneId + * @return success response or error + */ + @Operation( + summary = "Save company locale info", + description = "Updates the locale (language and timezone) for the current company. " + + "This sets the default language and timezone for the system." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "Locale settings updated successfully", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ResponseEntityStringView.class))), + @ApiResponse(responseCode = "400", + description = "Invalid locale parameters (e.g. invalid timezone)", + content = @Content(mediaType = "application/json")), + @ApiResponse(responseCode = "401", + description = "Unauthorized - CMS Administrator role required", + content = @Content(mediaType = "application/json")) + }) + @POST + @Path("/_saveCompanyLocaleInfo") + @JSONP + @NoCache + @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) + @Consumes(MediaType.APPLICATION_JSON) + public Response saveCompanyLocaleInfo( + @Context final HttpServletRequest request, + @Context final HttpServletResponse response, + @RequestBody(description = "Locale settings to apply", required = true, + content = @Content(schema = @Schema(implementation = CompanyLocaleForm.class))) + final CompanyLocaleForm form) { + + final InitDataObject initData = new InitBuilder(request, response) + .requiredRoles(Role.CMS_ADMINISTRATOR_ROLE) + .requiredPortlet("maintenance") + .rejectWhenNoUser(true) + .init(); + + if (!UtilMethods.isSet(form.getLanguageId()) || !UtilMethods.isSet(form.getTimeZoneId())) { + return ExceptionMapperUtil.createResponse( + "Both languageId and timeZoneId are required", + Response.Status.BAD_REQUEST); + } + + try { + helper.saveCompanyLocaleInfo(form.getLanguageId(), form.getTimeZoneId(), initData.getUser()); + return Response.ok(new ResponseEntityView<>(OK)).build(); + } catch (InvalidTimeZoneException e) { + Logger.error(this, "Invalid timezone: " + e.getMessage()); + return ExceptionMapperUtil.createResponse(e.getMessage(), Response.Status.BAD_REQUEST); + } catch (Exception e) { + Logger.error(this, "Error saving locale information: " + e.getMessage(), e); + return ExceptionMapperUtil.createResponse(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + }