diff --git a/data-upgrade-navigations/src/main/java/org/exoplatform/migration/PortalNavigationIconMigration.java b/data-upgrade-navigations/src/main/java/org/exoplatform/migration/PortalNavigationIconMigration.java
new file mode 100644
index 000000000..573547d8b
--- /dev/null
+++ b/data-upgrade-navigations/src/main/java/org/exoplatform/migration/PortalNavigationIconMigration.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 eXo Platform SAS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.exoplatform.migration;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.exoplatform.commons.api.persistence.ExoTransactional;
+import org.exoplatform.commons.persistence.impl.EntityManagerService;
+import org.exoplatform.commons.upgrade.UpgradeProductPlugin;
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.services.log.ExoLogger;
+import org.exoplatform.services.log.Log;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.Query;
+
+/**
+ * This plugin will be executed in order to set icons for the portal navigation
+ * nodes as provided in the configuration
+ */
+public class PortalNavigationIconMigration extends UpgradeProductPlugin {
+
+ private static final Log LOG = ExoLogger.getExoLogger(PortalNavigationIconMigration.class);
+
+ private static final String ICON_UPDATE_SQL =
+ """
+ UPDATE PORTAL_NAVIGATION_NODES
+ SET ICON =
+ CASE
+ %s
+ END
+ WHERE ICON IS NULL
+ AND EXISTS (SELECT * FROM PORTAL_PAGES p INNER JOIN PORTAL_SITES s ON s.ID = p.SITE_ID WHERE PAGE_ID = p.ID AND s.TYPE = 0 AND s.NAME LIKE 'dw')
+ """;
+
+ private static final String ICON_UPDATE_CASE_SQL = """
+ WHEN NAME in (%s) THEN TRIM('%s')
+ """;
+
+ private static final String PORTAL_NODE_NAMES = "portal.node.names";
+
+ private static final String PORTAL_NODE_ICONS = "portal.node.icons";
+
+ private final EntityManagerService entityManagerService;
+
+ private final Map portalNodes = new HashMap<>();
+
+ private int migratedPortalNodeIcons;
+
+ public PortalNavigationIconMigration(EntityManagerService entityManagerService, InitParams initParams) {
+ super(initParams);
+ this.entityManagerService = entityManagerService;
+ if (initParams.containsKey(PORTAL_NODE_ICONS) && initParams.containsKey(PORTAL_NODE_NAMES)) {
+ String[] portalNodeNames = initParams.getValueParam(PORTAL_NODE_NAMES).getValue().split(";");
+ String[] portalNodeIcons = initParams.getValueParam(PORTAL_NODE_ICONS).getValue().split(";");
+ if (portalNodeIcons.length == portalNodeNames.length) {
+ for (int i = 0; i < portalNodeNames.length; i++) {
+ this.portalNodes.put(portalNodeNames[i], portalNodeIcons[i]);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldProceedToUpgrade(String newVersion, String previousVersion) {
+ return !portalNodes.isEmpty();
+ }
+
+ @Override
+ public void processUpgrade(String oldVersion, String newVersion) {
+
+ long startupTime = System.currentTimeMillis();
+
+ LOG.info("Start:: Upgrade of portal navigation node icons");
+ Set> portalNodesEntrySet = portalNodes.entrySet();
+ this.migratedPortalNodeIcons = upgradePortalNodeIcons(portalNodesEntrySet);
+ LOG.info("End:: Upgrade of '{}' node icons. It tooks {} ms",
+ migratedPortalNodeIcons,
+ (System.currentTimeMillis() - startupTime));
+ }
+
+ @ExoTransactional
+ public int upgradePortalNodeIcons(Set> portalNodesEntrySet) {
+ EntityManager entityManager = entityManagerService.getEntityManager();
+
+ String sqlStatement = String.format(ICON_UPDATE_SQL, portalNodes.entrySet().stream().map(e -> {
+ String keys = Arrays.stream(e.getKey().split(",")).map(key -> String.format("'%s'", key)).collect(Collectors.joining(","));
+ return String.format(ICON_UPDATE_CASE_SQL, keys, e.getValue());
+ }).collect(Collectors.joining()));
+ Query query = entityManager.createNativeQuery(sqlStatement);
+ return query.executeUpdate();
+ }
+
+ public int getMigratedPortalNodeIconsNodeIcons() {
+ return migratedPortalNodeIcons;
+ }
+}
diff --git a/data-upgrade-navigations/src/main/resources/conf/portal/configuration.xml b/data-upgrade-navigations/src/main/resources/conf/portal/configuration.xml
index d78753906..1fc6479c1 100644
--- a/data-upgrade-navigations/src/main/resources/conf/portal/configuration.xml
+++ b/data-upgrade-navigations/src/main/resources/conf/portal/configuration.xml
@@ -195,5 +195,48 @@
+
+ PortalNavigationIconUpgradePlugin
+ addUpgradePlugin
+ org.exoplatform.migration.PortalNavigationIconMigration
+ Configure portal node icons
+
+
+ product.group.id
+ The groupId of the product
+ org.exoplatform.platform
+
+
+ plugin.upgrade.target.version
+ The plugin target version (will not be executed if previous version is equal or higher than 6.6.0)
+ 6.6.0
+
+
+ portal.node.names
+ The plugin will set the icons of these portal nodes names
+ external-stream
+
+
+ portal.node.icons
+ The plugin will set the portal nodes icons with these icons
+ fas fa-user-lock
+
+
+ plugin.execution.order
+ The plugin execution order
+ 101
+
+
+ plugin.upgrade.execute.once
+ The plugin must be executed only once
+ true
+
+
+ plugin.upgrade.async.execution
+ The plugin will be executed in an asynchronous mode
+ true
+
+
+
diff --git a/data-upgrade-navigations/src/test/java/org/exoplatform/migration/PortalNavigationIconMigrationTest.java b/data-upgrade-navigations/src/test/java/org/exoplatform/migration/PortalNavigationIconMigrationTest.java
new file mode 100644
index 000000000..c6d266086
--- /dev/null
+++ b/data-upgrade-navigations/src/test/java/org/exoplatform/migration/PortalNavigationIconMigrationTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 eXo Platform SAS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.exoplatform.migration;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.exoplatform.commons.persistence.impl.EntityManagerService;
+import org.exoplatform.component.test.AbstractKernelTest;
+import org.exoplatform.component.test.ConfigurationUnit;
+import org.exoplatform.component.test.ConfiguredBy;
+import org.exoplatform.component.test.ContainerScope;
+import org.exoplatform.container.PortalContainer;
+import org.exoplatform.container.component.RequestLifeCycle;
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.container.xml.ValueParam;
+import org.exoplatform.portal.jdbc.entity.NodeEntity;
+import org.exoplatform.portal.jdbc.entity.PageEntity;
+import org.exoplatform.portal.jdbc.entity.SiteEntity;
+import org.exoplatform.portal.mop.NodeTarget;
+import org.exoplatform.portal.mop.PageType;
+import org.exoplatform.portal.mop.SiteType;
+import org.exoplatform.portal.mop.dao.NodeDAO;
+import org.exoplatform.portal.mop.dao.PageDAO;
+import org.exoplatform.portal.mop.dao.SiteDAO;
+
+@ConfiguredBy({ @ConfigurationUnit(scope = ContainerScope.PORTAL, path = "conf/portal/configuration.xml"),
+ @ConfigurationUnit(scope = ContainerScope.PORTAL, path = "conf/exo.portal.component.portal-configuration-local.xml"),
+ @ConfigurationUnit(scope = ContainerScope.PORTAL, path = "org/exoplatform/portal/config/conf/configuration.xml") })
+public class PortalNavigationIconMigrationTest extends AbstractKernelTest {
+ InitParams initParams = new InitParams();
+ private PortalContainer container;
+ private EntityManagerService entityManagerService;
+ private PortalNavigationIconMigration portalNavigationIconMigration;
+ private SiteDAO siteDAO;
+ private PageDAO pageDAO;
+ private NodeDAO nodeDAO;
+
+ @Before
+ public void setUp() {
+ container = getContainer();
+ siteDAO = container.getComponentInstanceOfType(SiteDAO.class);
+ pageDAO = container.getComponentInstanceOfType(PageDAO.class);
+ nodeDAO = container.getComponentInstanceOfType(NodeDAO.class);
+ entityManagerService = container.getComponentInstanceOfType(EntityManagerService.class);
+ begin();
+ ValueParam productGroupIdValueParam = new ValueParam();
+ productGroupIdValueParam.setName("product.group.id");
+ productGroupIdValueParam.setValue("org.exoplatform.platform");
+ ValueParam portalNodeNamesValueParam = new ValueParam();
+ portalNodeNamesValueParam.setName("portal.node.names");
+ portalNodeNamesValueParam.setValue("external-stream");
+ ValueParam portalNodeIconsValueParam = new ValueParam();
+ portalNodeIconsValueParam.setName("portal.node.icons");
+ portalNodeIconsValueParam.setValue("fas fa-user-lock");
+ initParams.addParameter(productGroupIdValueParam);
+ initParams.addParameter(portalNodeNamesValueParam);
+ initParams.addParameter(portalNodeIconsValueParam);
+ this.portalNavigationIconMigration = new PortalNavigationIconMigration(entityManagerService, initParams);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ end();
+ }
+
+ @Test
+ public void testPortalNavigationIconMigration() {
+
+ SiteEntity siteEntity = new SiteEntity();
+ siteEntity.setName("dw");
+ siteEntity.setSiteType(SiteType.PORTAL);
+ siteDAO.create(siteEntity);
+ siteEntity = siteDAO.findByType(SiteType.PORTAL).stream().filter(e -> e.getName().equals("dw")).toList().get(0);
+ //
+ assertNotNull(siteEntity);
+ PageEntity pageEntity = new PageEntity();
+ pageEntity.setName("stream");
+ pageEntity.setOwner(siteEntity);
+ pageEntity.setPageType(PageType.PAGE);
+ pageDAO.create(pageEntity);
+ pageEntity = pageDAO.findAll().stream().filter(e -> e.getName().equals("stream")).toList().get(0);
+ //
+ assertNotNull(pageEntity);
+ NodeEntity nodeEntity = new NodeEntity();
+ nodeEntity.setName("external-stream");
+ nodeEntity.setIcon(null);
+ nodeEntity.setPage(pageEntity);
+ nodeEntity.setTarget(NodeTarget.NEW_TAB);
+ nodeDAO.create(nodeEntity);
+ nodeEntity = nodeDAO.findAllByPage(pageEntity.getId()).stream().filter(e -> e.getName().equals("external-stream")).toList().get(0);
+ //
+ assertNotNull(nodeEntity);
+ restartTransaction();
+ portalNavigationIconMigration.processUpgrade(null, null);
+ //
+ assertEquals(1, portalNavigationIconMigration.getMigratedPortalNodeIconsNodeIcons());
+ nodeEntity = nodeDAO.find(nodeEntity.getId());
+ assertNotNull(nodeEntity);
+ assertNotNull(nodeEntity.getIcon());
+ assertEquals("fas fa-user-lock", nodeEntity.getIcon());
+ }
+
+}