diff --git a/sass/internal-page/InternalPage.scss b/sass/internal-page/InternalPage.scss
index f6147a57c..2f966b62c 100644
--- a/sass/internal-page/InternalPage.scss
+++ b/sass/internal-page/InternalPage.scss
@@ -307,6 +307,41 @@
// sass-lint:enable force-element-nesting class-name-format nesting-depth
+// sass-lint:disable class-name-format force-element-nesting nesting-depth no-important no-qualifying-elements quotes property-sort-order
+.nav-pf-secondary-nav .list-group {
+ li.list-group-item.LegacyPlugins__group-header {
+ position: relative;
+ margin-top: 4px;
+ padding-left: 25px;
+ pointer-events: none;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 20px;
+ left: 20px;
+ height: 1px;
+ background-color: $color-pf-black-600;
+ }
+
+ &:first-of-type {
+ margin-top: 0;
+
+ &::before {
+ display: none;
+ }
+ }
+
+ > a {
+ padding-left: 20px !important;
+ font-weight: 700 !important;
+ cursor: default;
+ }
+ }
+}
+// sass-lint:enable class-name-format force-element-nesting nesting-depth no-important no-qualifying-elements quotes property-sort-order
+
.NoAccessPage {
position: fixed;
top: 0;
diff --git a/sass/widgets/config/WidgetConfigPage.scss b/sass/widgets/config/WidgetConfigPage.scss
index bb7133572..cf589340c 100644
--- a/sass/widgets/config/WidgetConfigPage.scss
+++ b/sass/widgets/config/WidgetConfigPage.scss
@@ -56,4 +56,11 @@
& .panel-heading {
background-color: $color-white;
}
+
+ &__legacy-iframe {
+ width: 100%;
+ min-height: 600px;
+ border: 0;
+ overflow: hidden;
+ }
}
diff --git a/src/api/system.js b/src/api/system.js
index 198dc2a84..8cdcc4540 100644
--- a/src/api/system.js
+++ b/src/api/system.js
@@ -2,7 +2,7 @@ import { makeRequest, METHODS } from '@entando/apimanager';
// eslint-disable-next-line import/prefer-default-export
export const getSystemReport = () => makeRequest({
- uri: '/api/system/report',
+ uri: '/api/system/legacy-components-menu',
method: METHODS.GET,
mockResponse: {},
useAuthentication: true,
diff --git a/src/helpers/legacyWidget.js b/src/helpers/legacyWidget.js
new file mode 100644
index 000000000..c0007c163
--- /dev/null
+++ b/src/helpers/legacyWidget.js
@@ -0,0 +1,20 @@
+import getAppBuilderWidgetForm from 'helpers/getAppBuilderWidgetForm';
+import { adminConsoleUrl } from 'helpers/urlUtils';
+
+export const LEGACY_CONFIG_CUSTOM_ELEMENT = 'LEGACY_CONFIG';
+
+// A widget is "legacy" when its configUi.customElement is set to LEGACY_CONFIG
+// and it has no internal app-builder form registered in getAppBuilderWidgetForm.
+export const isLegacyWidget = (widget) => {
+ if (!widget || !widget.configUi || widget.configUi.customElement !== 'LEGACY_CONFIG') {
+ return false;
+ }
+ return !getAppBuilderWidgetForm(widget);
+};
+
+export const buildLegacyConfigUrl = (widget, pageCode, frameId) => {
+ const actionConf = widget.configUiName || 'configSimpleParameter';
+ const path =
+ `do/Page/SpecialWidget/${actionConf}?pageCode=${pageCode}&widgetTypeCode=${widget.code}&frame=${frameId}&entandoHeadless=true`;
+ return adminConsoleUrl(path);
+};
diff --git a/src/helpers/permissions.js b/src/helpers/permissions.js
new file mode 100644
index 000000000..a6a84c400
--- /dev/null
+++ b/src/helpers/permissions.js
@@ -0,0 +1,43 @@
+/**
+ * Evaluates a boolean permission expression against user permissions.
+ * Supports & (AND), | (OR), and parentheses for grouping.
+ * Example: "(editContents|validateContents)&superuser"
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const checkPermission = (expr, userPermissions) => {
+ if (!expr) return false;
+ let i = 0;
+
+ const parseOr = () => {
+ // eslint-disable-next-line no-use-before-define
+ let result = parseAnd();
+ // eslint-disable-next-line no-plusplus,no-use-before-define
+ while (expr[i] === '|') { i++; result = parseAnd() || result; }
+ return result;
+ };
+
+ const parseAnd = () => {
+ // eslint-disable-next-line no-use-before-define
+ let result = parseAtom();
+ // eslint-disable-next-line no-plusplus,no-use-before-define
+ while (expr[i] === '&') { i++; result = parseAtom() && result; }
+ return result;
+ };
+
+ const parseAtom = () => {
+ if (expr[i] === '(') {
+ // eslint-disable-next-line no-plusplus
+ i++;
+ const result = parseOr();
+ // eslint-disable-next-line no-plusplus
+ i++;
+ return result;
+ }
+ let name = '';
+ // eslint-disable-next-line no-plusplus
+ while (i < expr.length && /\w/.test(expr[i])) name += expr[i++];
+ return userPermissions.includes(name);
+ };
+
+ return parseOr();
+};
diff --git a/src/locales/en.js b/src/locales/en.js
index 13c5f1c00..15712c1f5 100644
--- a/src/locales/en.js
+++ b/src/locales/en.js
@@ -337,6 +337,7 @@ export default {
'menu.scheduler': 'Scheduler',
'menu.versioning': 'Versioning',
'menu.apps': 'Apps',
+ 'menu.legacyPlugins': 'Legacy Plugins',
'menu.settings': 'Administration',
'menu.emailConfig': 'Email Configuration',
'dashboard.cards.noPermission': 'You have no permission to visualize this data',
@@ -411,6 +412,7 @@ export default {
'widget.page.config.error': 'Unable to load widget configuration',
'widget.page.config.goToConfig': 'Go to widget default configuration',
'widget.page.config.readOnlyMessage': 'This is the default configuration for this widget. Do you want to change it?',
+ 'widget.page.config.legacySaveTooltip': 'Use the Save button inside the configuration form below',
'widget.icon.description': 'Upload your Icon from the Icon Library or your Computer',
'widget.icon.iconLibrary': 'Icon Library',
'widget.icon.upload': 'Upload',
diff --git a/src/locales/it.js b/src/locales/it.js
index 57fb5f719..596382a92 100644
--- a/src/locales/it.js
+++ b/src/locales/it.js
@@ -337,6 +337,7 @@ export default {
'menu.scheduler': 'Scheduler',
'menu.versioning': 'Versionamento',
'menu.apps': 'App',
+ 'menu.legacyPlugins': 'Plugin Legacy',
'menu.settings': 'Amministrazione',
'menu.emailConfig': 'Configurazione Email',
'dashboard.cards.noPermission': 'Non hai il permesso di visualizzare questi dati',
@@ -411,6 +412,7 @@ export default {
'widget.page.config.error': 'Impossibile caricare la configurazione del widget',
'widget.page.config.goToConfig': 'Vai alla configurazione di default',
'widget.page.config.readOnlyMessage': 'Questa è la configurazione di default del widget. Vuoi modificarla?',
+ 'widget.page.config.legacySaveTooltip': 'Usa il pulsante Salva nel modulo di configurazione qui sotto',
'widget.icon.description': 'Carica la tua icona dalla libreria delle icone o dal tuo computer',
'widget.icon.iconLibrary': 'Icon Library',
'widget.icon.upload': 'Carica',
diff --git a/src/locales/pt.js b/src/locales/pt.js
index 5c9fd69bb..70adbb922 100644
--- a/src/locales/pt.js
+++ b/src/locales/pt.js
@@ -326,6 +326,7 @@ export default {
'menu.apiManagement.resources': 'Recursos',
'menu.apiManagement.services': 'serviços',
'menu.apiManagement.consumers': 'Consumer',
+ 'menu.legacyPlugins': 'Plugins Legados',
'menu.settings': 'Administração',
'menu.emailConfig': 'Configuração de Email',
'menu.apps': 'Apps',
diff --git a/src/state/page-config/actions.js b/src/state/page-config/actions.js
index a32781303..e5792ab13 100644
--- a/src/state/page-config/actions.js
+++ b/src/state/page-config/actions.js
@@ -2,6 +2,7 @@ import { initialize } from 'redux-form';
import { routeConverter } from '@entando/utils';
import { addToast, addErrors, clearErrors, TOAST_ERROR } from '@entando/messages';
+import { isLegacyWidget } from 'helpers/legacyWidget';
import { loadSelectedPageTemplate } from 'state/page-templates/actions';
import { getSelectedPageTemplateMainFrame, getSelectedPageTemplateDefaultConfig } from 'state/page-templates/selectors';
import { loadSelectedPage, setSelectedPage } from 'state/pages/actions';
@@ -254,10 +255,12 @@ export const configOrUpdatePageWidget = (sourceWidgetId, sourceFrameId, targetFr
if (widget.hasConfig && !isAlreadyConfigured) {
const nextStep = widgetNextSteps[sourceWidgetId];
const appTourProgress = getAppTourProgress(getState());
+
history.push(routeConverter(
ROUTE_WIDGET_CONFIG,
{ pageCode, widgetCode: sourceWidgetId, framePos: targetFrameId },
));
+
if (nextStep && appTourProgress === APP_TOUR_STARTED) {
dispatch(setAppTourLastStep(nextStep));
}
@@ -277,7 +280,7 @@ export const editWidgetConfig = (frameId, pageCode) =>
const widget = getWidgetsMap(getState())[pageConfigItem.code];
const isConfigurableWidget = widget && widget.hasConfig;
- if (isConfigurableWidget || pageConfigItem.config) {
+ if (isLegacyWidget(widget) || isConfigurableWidget || pageConfigItem.config) {
dispatch(initialize('widgetConfigForm', pageConfigItem.config));
history.push(routeConverter(
ROUTE_WIDGET_CONFIG,
diff --git a/src/state/page-config/selectors.js b/src/state/page-config/selectors.js
index 4116f4c4a..1df14ed95 100644
--- a/src/state/page-config/selectors.js
+++ b/src/state/page-config/selectors.js
@@ -70,6 +70,7 @@ export const makeGetPageConfigCellMap = params => createSelector(
relatedCell.widgetTitle = widget.titles[locale] || widget.name;
relatedCell.widgetHasConfig = widget.hasConfig;
relatedCell.configUiName = widget.configUiName;
+ relatedCell.configUi = widget.configUi || null;
relatedCell.widgetHasConfigForm = !!isMicrofrontendWidgetForm(widget);
}
}
diff --git a/src/ui/internal-page/VerticalMenuContainer.js b/src/ui/internal-page/VerticalMenuContainer.js
index fdd4b59b2..250d5d809 100644
--- a/src/ui/internal-page/VerticalMenuContainer.js
+++ b/src/ui/internal-page/VerticalMenuContainer.js
@@ -9,6 +9,7 @@ import { routeConverter, hasAccess } from '@entando/utils';
import { clearAppTourProgress, setAppTourLastStep, setWizardEnabled } from 'state/app-tour/actions';
import { adminConsoleUrl } from 'helpers/urlUtils';
+import { checkPermission } from 'helpers/permissions';
import UserMenuContainer from 'ui/internal-page/UserMenuContainer';
import LanguageSelectContainer from 'ui/internal-page/LanguageSelectContainer';
@@ -54,6 +55,66 @@ const {
const publicUrl = process.env.PUBLIC_URL;
+const getReportEntries = systemReport => (Array.isArray(systemReport) ? systemReport : []);
+
+const getGroupItems = group =>
+ (Array.isArray(group['appBuilderMenu.items']) ? group['appBuilderMenu.items'] : []);
+
+const renderMenuItemsByHook = (systemReport, hook) =>
+ getReportEntries(systemReport).filter(e => e['appBuilderMenu.hook'] === hook);
+
+const renderDynamicItems = (items, intl, userPermissions) =>
+ items
+ .filter(item => checkPermission(item.requiredPermission, userPermissions))
+ .map(item => (
+
+ ));
+
+const renderLegacyPluginsMenu = (intl, userPermissions, systemReport) => {
+ const pluginGroups = renderMenuItemsByHook(systemReport, 'legacyPlugins')
+ .filter(g => getGroupItems(g)
+ .some(item => checkPermission(item.requiredPermission, userPermissions)));
+
+ if (pluginGroups.length === 0) return null;
+
+ return (
+
+ );
+};
+
const renderCmsMenuItems = (intl, userPermissions, systemReport, currSysConfigAdvancedSearchOn) => {
const hasMenuContentsAccess = hasAccess([
CRUD_CONTENTS_PERMISSION,
@@ -74,7 +135,8 @@ const renderCmsMenuItems = (intl, userPermissions, systemReport, currSysConfigAd
], userPermissions);
const hasMenuContentSettingsAccess = hasAccess(SUPERUSER_PERMISSION, userPermissions);
- const { contentSchedulerPluginInstalled, contentWorkFlowPluginInstalled } = systemReport;
+ const cmsGroups = renderMenuItemsByHook(systemReport, 'cms');
+ const cmsItems = cmsGroups.length > 0 ? getGroupItems(cmsGroups[0]) : [];
return (
)
}
- {
- hasMenuContentsAccess && contentSchedulerPluginInstalled && (
-
- )
- }
- {
- hasMenuContentsAccess && contentWorkFlowPluginInstalled && (
-
- )
- }
+ {renderDynamicItems(cmsItems, intl, userPermissions)}
{
hasMenuContentTypeAccess && (
{
widgetHasConfig={col.widgetHasConfig}
widgetStatus={col.widgetStatus}
widgetHasConfigForm={col.widgetHasConfigForm}
+ configUi={col.configUi}
/>
);
} else {
@@ -92,6 +93,9 @@ PageConfigGridCol.propTypes = {
widgetCode: PropTypes.string,
widgetStatus: PropTypes.string,
configUiName: PropTypes.string,
+ configUi: PropTypes.shape({
+ customElement: PropTypes.string,
+ }),
frameDescr: PropTypes.string,
widgetTitle: PropTypes.string,
widgetHasConfig: PropTypes.bool,
diff --git a/src/ui/pages/config/WidgetFrame.js b/src/ui/pages/config/WidgetFrame.js
index 7301ec951..53f93c067 100644
--- a/src/ui/pages/config/WidgetFrame.js
+++ b/src/ui/pages/config/WidgetFrame.js
@@ -7,6 +7,7 @@ import { routeConverter } from '@entando/utils';
import { ROUTE_WIDGET_DETAIL, ROUTE_WIDGET_EDIT } from 'app-init/router';
import { WIDGET_STATUS_MATCH, WIDGET_STATUS_DIFF, WIDGET_STATUS_REMOVED } from 'state/page-config/const';
+import { LEGACY_CONFIG_CUSTOM_ELEMENT } from 'helpers/legacyWidget';
import WidgetIcon from 'ui/widgets/common/WidgetIcon';
@@ -15,12 +16,14 @@ class WidgetFrame extends Component {
const {
widgetId, widgetName, widgetHasConfig, widgetStatus, frameId, frameName, frameIsMainFrame,
onClickDelete, connectDragSource, connectDropTarget, isOver, onClickSettings, onClickSaveAs,
- configUiName, widgetHasConfigForm,
+ configUiName, widgetHasConfigForm, configUi,
} = this.props;
+ const isLegacy = configUi && configUi.customElement === LEGACY_CONFIG_CUSTOM_ELEMENT;
+
let actionsMenu = null;
if (widgetStatus !== WIDGET_STATUS_REMOVED) {
- const configMenuItems = widgetHasConfig && (configUiName || widgetHasConfigForm) ?
+ const configMenuItems = widgetHasConfig && (configUiName || widgetHasConfigForm || isLegacy) ?
[
(