diff --git a/component/api/pom.xml b/component/api/pom.xml
index cc1963c8843..9c3d336908d 100644
--- a/component/api/pom.xml
+++ b/component/api/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-component-api
Meeds:: PLF:: Social API
diff --git a/component/common/pom.xml b/component/common/pom.xml
index d997d1baeb1..e1351d9a3d6 100644
--- a/component/common/pom.xml
+++ b/component/common/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
io.meeds.social
social-component-common
diff --git a/component/core/pom.xml b/component/core/pom.xml
index 916070138fc..40f37f5c373 100644
--- a/component/core/pom.xml
+++ b/component/core/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-component-core
Meeds:: PLF:: Social Core Component
diff --git a/component/core/src/main/java/io/meeds/social/space/plugin/SpaceCategoryPlugin.java b/component/core/src/main/java/io/meeds/social/space/plugin/SpaceCategoryPlugin.java
index de837609cbd..637406f0fc3 100644
--- a/component/core/src/main/java/io/meeds/social/space/plugin/SpaceCategoryPlugin.java
+++ b/component/core/src/main/java/io/meeds/social/space/plugin/SpaceCategoryPlugin.java
@@ -20,11 +20,17 @@
import java.util.List;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import org.exoplatform.social.core.identity.model.Identity;
+import org.exoplatform.social.core.identity.provider.SpaceIdentityProvider;
+import org.exoplatform.social.core.manager.IdentityManager;
+import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
+import io.meeds.social.category.model.CategoryObject;
import io.meeds.social.category.plugin.CategoryPlugin;
@Component
@@ -35,6 +41,9 @@ public class SpaceCategoryPlugin implements CategoryPlugin {
@Autowired
private SpaceService spaceService;
+ @Autowired
+ private IdentityManager identityManager;
+
@Override
public String getType() {
return OBJECT_TYPE;
@@ -55,4 +64,63 @@ public List getCategoryIds() {
return spaceService.getSpaceCategoryIds();
}
+ /**
+ * Normalizes the identifier carried by the incoming {@link CategoryObject} to
+ * the canonical space technical id ({@link Space#getSpaceId()}) before the
+ * link is stored, read back or broadcast. Category links are stored generically
+ * on {@code (type, id)}, but the space UI/REST relies on the denormalized
+ * {@code space.getCategoryIds()} that is only kept in sync by
+ * {@code CategoryLinkModifiedListener}, whose SPACE branch resolves the space
+ * through {@code spaceService.getSpaceById(object.getId())} and therefore
+ * requires the technical id. Callers (e.g. the MCP tooling) may pass the space
+ * identity id or its pretty name instead; normalizing here makes the write, the
+ * read-back and the denormalization listener all agree whichever id form was
+ * supplied.
+ *
+ * @param categoryObject The incoming {@link CategoryObject} whose id may be a
+ * space technical id, a social {@link Identity} id or a pretty name
+ * @return A {@link CategoryObject} carrying the canonical space technical id,
+ * or the object unchanged when the id cannot be resolved to a space
+ **/
+ @Override
+ public CategoryObject getObject(CategoryObject categoryObject) {
+ Space space = resolveSpace(categoryObject.getId());
+ if (space == null) {
+ return categoryObject;
+ }
+ return new CategoryObject(categoryObject.getType(),
+ String.valueOf(space.getSpaceId()),
+ categoryObject.getParentId(),
+ categoryObject.getSpaceId());
+ }
+
+ /**
+ * Resolves a {@link Space} from any of the id forms a space can be referenced
+ * by across the platform: a space technical id, a social {@link Identity} id
+ * (mapped to its space through the space identity provider's remote id, i.e.
+ * the space pretty name) or a pretty name. This mirrors the space resolution
+ * idiom used by {@code SpacePermanentLinkPlugin#getDirectAccessUrl}.
+ *
+ * @param id A space technical id, social {@link Identity} id or pretty name
+ * @return The resolved {@link Space}, or {@code null} when none matches
+ **/
+ private Space resolveSpace(String id) {
+ if (StringUtils.isBlank(id)) {
+ return null;
+ }
+ if (StringUtils.isNumeric(id)) {
+ Space space = spaceService.getSpaceById(id);
+ if (space != null) {
+ return space;
+ }
+ // Fall back to a social Identity id pointing at the space
+ Identity identity = identityManager.getIdentity(id);
+ if (identity != null && SpaceIdentityProvider.NAME.equals(identity.getProviderId())) {
+ return spaceService.getSpaceByPrettyName(identity.getRemoteId());
+ }
+ return null;
+ }
+ return spaceService.getSpaceByPrettyName(id);
+ }
+
}
diff --git a/component/core/src/test/java/io/meeds/social/category/service/CategoryLinkServiceTest.java b/component/core/src/test/java/io/meeds/social/category/service/CategoryLinkServiceTest.java
index b7690bf8412..70156f39069 100644
--- a/component/core/src/test/java/io/meeds/social/category/service/CategoryLinkServiceTest.java
+++ b/component/core/src/test/java/io/meeds/social/category/service/CategoryLinkServiceTest.java
@@ -25,6 +25,7 @@
import java.util.Collections;
import java.util.Locale;
+import org.apache.commons.collections4.CollectionUtils;
import org.junit.Test;
import org.exoplatform.commons.ObjectAlreadyExistsException;
@@ -129,4 +130,30 @@ public void testUnLink() {
assertFalse(categoryLinkService.isLinked(rootCategory.getId(), object));
}
+ @Test
+ @SneakyThrows
+ public void testLinkBySpaceIdentityIdDenormalizesCategoryIds() {
+ Space space = new Space();
+ space.setRegistration(Space.OPEN);
+ space.setVisibility(Space.PUBLIC);
+ space = spaceService.createSpace(space, ROOT_USER);
+
+ Category rootCategory = categoryService.getRootCategory(getAdminGroupIdentityId());
+
+ // Link the space using its social Identity id (how spaces are referenced
+ // across the platform) instead of its technical id, as the MCP tooling does
+ String spaceIdentityId = identityManager.getOrCreateSpaceIdentity(space.getPrettyName()).getId();
+ CategoryObject object = new CategoryObject(SpaceCategoryPlugin.OBJECT_TYPE, spaceIdentityId, space.getSpaceId());
+ categoryLinkService.link(rootCategory.getId(), object, ROOT_USER);
+
+ // The link is stored/read-back under the normalized technical id
+ assertTrue(categoryLinkService.isLinked(rootCategory.getId(), object));
+
+ // And the denormalization listener kept space.categoryIds in sync
+ restartTransaction();
+ Space updatedSpace = spaceService.getSpaceById(space.getId());
+ assertTrue(CollectionUtils.isNotEmpty(updatedSpace.getCategoryIds())
+ && updatedSpace.getCategoryIds().contains(rootCategory.getId()));
+ }
+
}
diff --git a/component/core/src/test/java/io/meeds/social/space/plugin/SpaceCategoryPluginUnitTest.java b/component/core/src/test/java/io/meeds/social/space/plugin/SpaceCategoryPluginUnitTest.java
new file mode 100644
index 00000000000..0cd4c936967
--- /dev/null
+++ b/component/core/src/test/java/io/meeds/social/space/plugin/SpaceCategoryPluginUnitTest.java
@@ -0,0 +1,120 @@
+/**
+ * This file is part of the Meeds project (https://meeds.io/).
+ *
+ * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package io.meeds.social.space.plugin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import org.exoplatform.social.core.identity.model.Identity;
+import org.exoplatform.social.core.identity.provider.SpaceIdentityProvider;
+import org.exoplatform.social.core.manager.IdentityManager;
+import org.exoplatform.social.core.space.model.Space;
+import org.exoplatform.social.core.space.spi.SpaceService;
+
+import io.meeds.social.category.model.CategoryObject;
+
+@SpringBootTest(classes = {
+ SpaceCategoryPlugin.class,
+})
+@RunWith(SpringRunner.class)
+public class SpaceCategoryPluginUnitTest {
+
+ private static final long SPACE_TECHNICAL_ID = 42l;
+
+ private static final long SPACE_IDENTITY_ID = 74456l;
+
+ private static final String SPACE_PRETTY_NAME = "prettyName";
+
+ @MockBean
+ private SpaceService spaceService;
+
+ @MockBean
+ private IdentityManager identityManager;
+
+ @Autowired
+ private SpaceCategoryPlugin spaceCategoryPlugin;
+
+ @Test
+ public void testGetObjectNormalizesTechnicalId() {
+ Space space = newSpace();
+ when(spaceService.getSpaceById(String.valueOf(SPACE_TECHNICAL_ID))).thenReturn(space);
+
+ CategoryObject object = new CategoryObject(SpaceCategoryPlugin.OBJECT_TYPE, String.valueOf(SPACE_TECHNICAL_ID), 0l);
+ CategoryObject normalized = spaceCategoryPlugin.getObject(object);
+
+ assertEquals(String.valueOf(SPACE_TECHNICAL_ID), normalized.getId());
+ assertEquals(SpaceCategoryPlugin.OBJECT_TYPE, normalized.getType());
+ }
+
+ @Test
+ public void testGetObjectNormalizesIdentityIdToTechnicalId() {
+ Space space = newSpace();
+ // The identity id is not a space technical id
+ when(spaceService.getSpaceById(String.valueOf(SPACE_IDENTITY_ID))).thenReturn(null);
+ Identity identity = mock(Identity.class);
+ when(identity.getProviderId()).thenReturn(SpaceIdentityProvider.NAME);
+ when(identity.getRemoteId()).thenReturn(SPACE_PRETTY_NAME);
+ when(identityManager.getIdentity(String.valueOf(SPACE_IDENTITY_ID))).thenReturn(identity);
+ when(spaceService.getSpaceByPrettyName(SPACE_PRETTY_NAME)).thenReturn(space);
+
+ CategoryObject object = new CategoryObject(SpaceCategoryPlugin.OBJECT_TYPE, String.valueOf(SPACE_IDENTITY_ID), 0l);
+ CategoryObject normalized = spaceCategoryPlugin.getObject(object);
+
+ assertEquals(String.valueOf(SPACE_TECHNICAL_ID), normalized.getId());
+ }
+
+ @Test
+ public void testGetObjectNormalizesPrettyNameToTechnicalId() {
+ Space space = newSpace();
+ when(spaceService.getSpaceByPrettyName(SPACE_PRETTY_NAME)).thenReturn(space);
+
+ CategoryObject object = new CategoryObject(SpaceCategoryPlugin.OBJECT_TYPE, SPACE_PRETTY_NAME, 0l);
+ CategoryObject normalized = spaceCategoryPlugin.getObject(object);
+
+ assertEquals(String.valueOf(SPACE_TECHNICAL_ID), normalized.getId());
+ }
+
+ @Test
+ public void testGetObjectReturnsSameObjectWhenUnresolvable() {
+ when(spaceService.getSpaceById("unknown")).thenReturn(null);
+ when(spaceService.getSpaceByPrettyName("unknown")).thenReturn(null);
+
+ CategoryObject object = new CategoryObject(SpaceCategoryPlugin.OBJECT_TYPE, "unknown", 0l);
+ CategoryObject normalized = spaceCategoryPlugin.getObject(object);
+
+ assertSame(object, normalized);
+ }
+
+ private Space newSpace() {
+ Space space = new Space();
+ space.setId(String.valueOf(SPACE_TECHNICAL_ID));
+ space.setPrettyName(SPACE_PRETTY_NAME);
+ return space;
+ }
+
+}
diff --git a/component/notification/pom.xml b/component/notification/pom.xml
index 2ca9bbaac27..52a57721001 100644
--- a/component/notification/pom.xml
+++ b/component/notification/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-component-notification
Meeds:: PLF:: Social Notification Component
diff --git a/component/oauth-auth/pom.xml b/component/oauth-auth/pom.xml
index c7bb2a3e80f..18966e414f2 100644
--- a/component/oauth-auth/pom.xml
+++ b/component/oauth-auth/pom.xml
@@ -21,7 +21,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
4.0.0
diff --git a/component/pom.xml b/component/pom.xml
index b5976751ba8..c851837af89 100644
--- a/component/pom.xml
+++ b/component/pom.xml
@@ -22,7 +22,7 @@
social
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-component
pom
diff --git a/component/service/pom.xml b/component/service/pom.xml
index c6b52444ca4..166c7ac720d 100644
--- a/component/service/pom.xml
+++ b/component/service/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-component-service
Meeds:: PLF:: Social Service Component
diff --git a/component/web/pom.xml b/component/web/pom.xml
index 90866efe91e..e6ece9c6b6a 100644
--- a/component/web/pom.xml
+++ b/component/web/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
io.meeds.social
social-component-web
diff --git a/pom.xml b/pom.xml
index ab09c7291e8..8d1b5df5275 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
io.meeds.social
social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
pom
Meeds:: PLF:: Social
Meeds Social - Enterprise Social Networking
@@ -45,8 +45,8 @@
- 7.3.x-SNAPSHOT
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
meeds-io
diff --git a/webapp/pom.xml b/webapp/pom.xml
index 4c6ca6afe37..bf1c5b46eb3 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -22,7 +22,7 @@
io.meeds.social
social
- 7.3.x-SNAPSHOT
+ 7.3.x-ai-contribution-SNAPSHOT
social-webapp
war
diff --git a/webapp/src/main/webapp/vue-apps/general-settings/components/branding/drawers/PageStylingDrawer.vue b/webapp/src/main/webapp/vue-apps/general-settings/components/branding/drawers/PageStylingDrawer.vue
index 3835d8e80f5..4a599ece67d 100644
--- a/webapp/src/main/webapp/vue-apps/general-settings/components/branding/drawers/PageStylingDrawer.vue
+++ b/webapp/src/main/webapp/vue-apps/general-settings/components/branding/drawers/PageStylingDrawer.vue
@@ -121,7 +121,7 @@ export default {
drawer: false,
backgroundProperties: null,
defaultCustomPageWidth: '1320',
- defaultPageBackground: '#F0F0F0',
+ defaultPageBackground: '#F0F0F0FF',
pageWidth: null,
borderRadius: null,
defaultPageStylingProperties: null,
@@ -164,7 +164,7 @@ export default {
methods: {
init() {
this.backgroundProperties = {
- backgroundColor: this.pageStylingProperties?.pageBackgroundColor || this.defaultPageBackground,
+ backgroundColor: this.normalizeBackgroundColor(this.pageStylingProperties?.pageBackgroundColor) || this.defaultPageBackground,
backgroundPosition: this.pageStylingProperties?.pageBackgroundPosition || null,
background: this.pageStylingProperties?.pageBackground || null,
backgroundRepeat: this.pageStylingProperties?.pageBackgroundRepeat || null,
@@ -205,6 +205,12 @@ export default {
this.reset();
this.$refs.drawer.close();
},
+ normalizeBackgroundColor(color) {
+ if (!color) {
+ return color;
+ }
+ return color.length === 7 ? `${color}FF` : color;
+ },
getPageBackgroundEffect() {
const effect = this.pageStylingProperties?.pageBackgroundEffect;
if (!effect || effect === 'none') {
diff --git a/webapp/src/main/webapp/vue-apps/general-settings/components/branding/form/BackgroundInput.vue b/webapp/src/main/webapp/vue-apps/general-settings/components/branding/form/BackgroundInput.vue
index 94a2f0dfa5b..6aa2371083e 100644
--- a/webapp/src/main/webapp/vue-apps/general-settings/components/branding/form/BackgroundInput.vue
+++ b/webapp/src/main/webapp/vue-apps/general-settings/components/branding/form/BackgroundInput.vue
@@ -225,7 +225,7 @@ export default {
this.backgroundGradientFrom = null;
this.backgroundGradientTo = null;
} else if (this.choice === 'gradient') {
- if (this.branding.backgroundEffect) {
+ if (this.branding.backgroundEffect && this.branding.backgroundEffect.startsWith('linear-gradient(')) {
this.backgroundGradientFrom = this.branding.backgroundEffect.replace('linear-gradient(', '').split(',')[0].trim();
this.backgroundGradientTo = this.branding.backgroundEffect.replace('linear-gradient(', '').split(',')[1].replace(/\)$/g, '').trim();
} else {
@@ -253,7 +253,7 @@ export default {
this.backgroundImageStyle = this.branding.backgroundRepeat;
}
}
- if (this.branding.backgroundEffect) {
+ if (this.branding.backgroundEffect && this.branding.backgroundEffect.startsWith('linear-gradient(')) {
this.choice = 'gradient';
this.backgroundGradientFrom = this.branding.backgroundEffect.replace('linear-gradient(', '').split(',')[0].trim();
this.backgroundGradientTo = this.branding.backgroundEffect.replace('linear-gradient(', '').split(',')[1].replace(/\)$/g, '').trim();