From ebe9f4eabb8231efdc1ac9a17bed3d5cc49f07bf Mon Sep 17 00:00:00 2001 From: ayoub cherif Date: Thu, 2 Jul 2026 15:20:43 +0100 Subject: [PATCH 1/3] task-88259: Create FB ai-contribution and update projects versions/dependencies --- component/api/pom.xml | 2 +- component/common/pom.xml | 2 +- component/core/pom.xml | 2 +- component/notification/pom.xml | 2 +- component/oauth-auth/pom.xml | 2 +- component/pom.xml | 2 +- component/service/pom.xml | 2 +- component/web/pom.xml | 2 +- pom.xml | 6 +++--- webapp/pom.xml | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) 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/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 From 89b11ed53fab1de8e7a4fe2b38666b008b416b17 Mon Sep 17 00:00:00 2001 From: Samuel Renault <58853195+srenault-meeds@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:17:09 +0200 Subject: [PATCH 2/3] fix: default page background lacks alpha support - EXO-88511 (#5873) The Theme management page's default page background color (used both as the picker's placeholder and normalized when reading an already persisted 6-digit value) had no alpha channel, so the color picker silently dropped its transparency slider. Also stop the gradient from/to parser from garbling non-linear-gradient values (e.g. conic gradients set elsewhere) instead of falling back to solid color. --- .../components/branding/drawers/PageStylingDrawer.vue | 10 ++++++++-- .../components/branding/form/BackgroundInput.vue | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) 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(); From 70db850e0b411aa2261726e6de437bd5e8012769 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 20 Jul 2026 00:19:40 +0100 Subject: [PATCH 3/3] fix: normalize space id in SpaceCategoryPlugin so category links apply to spaces - EXO-88506 Category links are generic metadata keyed on (objectType, objectId), but the space UI/REST reads a denormalized space.getCategoryIds() kept in sync only by CategoryLinkModifiedListener, whose SPACE branch resolves the space via spaceService.getSpaceById(object.getId()) and therefore requires the space technical id. SpaceCategoryPlugin did not override getObject(), so when a caller (e.g. the MCP tooling) passed the space identity id or its pretty name, the raw link was written/read-back under that id (false success) while the listener resolved the wrong space or null, leaving space.categoryIds untouched and the UI empty. Override CategoryPlugin.getObject() to normalize any incoming space id form (technical id, social Identity id or pretty name) to the canonical technical id String.valueOf(space.getSpaceId()). Since link()/getLinkedIds() and the listener all funnel through getObject(), the write, the read-back and the denormalization now agree regardless of which id was supplied. Unresolvable ids are returned unchanged rather than silently corrupted. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../space/plugin/SpaceCategoryPlugin.java | 68 ++++++++++ .../service/CategoryLinkServiceTest.java | 27 ++++ .../plugin/SpaceCategoryPluginUnitTest.java | 120 ++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 component/core/src/test/java/io/meeds/social/space/plugin/SpaceCategoryPluginUnitTest.java 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; + } + +}