Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ api.exception.resourceAlreadyExists=Resource already exists
api.exception.resourceAlreadyExists.description=Resource already exists.
api.exception.unsatisfiedRequestParameter=Unsatisfied request parameter
api.exception.unsatisfiedRequestParameter.description=Unsatisfied request parameter.
api.exception.invalidStaticPageIdReservedPrefix=Page identifier ''{0}'' starts with the reserved prefix ''{1}''. The ''{1}'' prefix is reserved for built-in GeoNetwork menu entries. Please choose a different identifier.
exception.maxUploadSizeExceeded=Maximum upload size of {0} exceeded.
exception.maxUploadSizeExceeded.description=The request was rejected because its size ({0}) exceeds the configured maximum ({1}).
exception.maxUploadSizeExceededUnknownSize.description=The request was rejected because its size exceeds the configured maximum ({0}).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ api.exception.resourceAlreadyExists=La ressource existe déjà
api.exception.resourceAlreadyExists.description=La ressource existe déjà.
api.exception.unsatisfiedRequestParameter=Paramètre de demande non satisfait
api.exception.unsatisfiedRequestParameter.description=Paramètre de demande non satisfait.
api.exception.invalidStaticPageIdReservedPrefix=L''identifiant de page ''{0}'' commence par le préfixe réservé ''{1}''. Le préfixe ''{1}'' est réservé aux entrées de menu GeoNetwork intégrées. Veuillez choisir un autre identifiant.
exception.maxUploadSizeExceeded=La taille maximale du téléchargement de {0} a été excédée.
exception.maxUploadSizeExceeded.description=La demande a été refusée car sa taille ({0}) excède le maximum configuré ({1}).
exception.maxUploadSizeExceededUnknownSize.description=La demande a été refusée car sa taille excède le maximum configuré ({0}).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ This section defines the configuration for the map shown when editing a record.
"gn-recordview-display-menu"]
```

Custom static pages can be inserted between these default entries. Static pages used there must be created for the `RECORD_VIEW_MENU` section. See [Adding static pages](../../customizing-application/adding-static-pages.md).
If this list is empty, only those default entries are shown and static pages are not added automatically.

Custom static pages can be inserted between these default entries. Static pages used there must be created for the `RECORD_VIEW_MENU` section and referenced by page identifier (for example `documentation`). The `gn-` prefix is reserved for built-in entries and should not be used for custom page identifiers. See [Adding static pages](../../customizing-application/adding-static-pages.md).

The `gn-recordview-manage-menu` entry contains record management actions and is only shown to users with editor privileges or above. The `gn-recordview-download-menu` entry contains download and export actions and is available whenever a record is displayed.

Expand Down
13 changes: 12 additions & 1 deletion docs/manual/docs/customizing-application/adding-static-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ To add new static pages go to **Admin Console** --> **Settings** --> **Static

- **Page section**: Section of the page to display the link. Currently implemented sections are `TOP` (top menu of the main page), `FOOTER` (footer of the main page), and `RECORD_VIEW_MENU` (toolbar shown on the record view page).

- **Workflow visibility (`RECORD_VIEW_MENU` only)**: Controls whether a page is shown for each workflow state.

- `showWhenWorkflowDisabled`
- `showOnApproved`
- `showOnNonApproved`

These options default to `true` (backward compatible).

- **Status**: Defines which users can see the link.

- **Visible only to the administrator**
Expand Down Expand Up @@ -103,6 +111,10 @@ By default, the record view toolbar uses the following menu order:
"gn-recordview-display-menu"]
```

If the **Record View custom menu items** list is empty, only this default menu order is used and static pages are not automatically added.

Use the static page identifier directly (for example `documentation`) when inserting custom pages. The `gn-` prefix is reserved for built-in menu entries and custom page identifiers using that prefix are ignored.

As with the top toolbar, insert a page as a simple menu using the page identifier or as a submenu using an object:

``` json
Expand All @@ -123,4 +135,3 @@ As with the top toolbar, insert a page as a simple menu using the page identifie
## Change the static pages order in the footer

The order of the footer pages can be configured in **Admin Console** --> **Settings** --> **User interface**, in the **Footer custom menu items** section.

30 changes: 30 additions & 0 deletions domain/src/main/java/org/fao/geonet/domain/page/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public class Page extends GeonetEntity implements Serializable {

private String label;
private String icon;
private boolean showOnNonApproved = true;
private boolean showOnApproved = true;
private boolean showWhenWorkflowDisabled = true;

public Page() {

Expand Down Expand Up @@ -155,6 +158,33 @@ public String getIcon() {
return icon;
}

@Column(nullable = false, columnDefinition = "boolean default true")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using db.migration_onstartup=false which sets hibernate.hbm2ddl.auto=validate, the new columns will not be created, probably better to add the new columns in the migration scripts.

On upgrade, Hibernate will find the entity declaring the new columns that don't exist in the DB and fail startup.

With the default db.migration_onstartup=true this problem will not happen.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in latest push

public boolean isShowOnNonApproved() {
return showOnNonApproved;
}

public void setShowOnNonApproved(boolean showOnNonApproved) {
this.showOnNonApproved = showOnNonApproved;
}

@Column(nullable = false, columnDefinition = "boolean default true")
public boolean isShowOnApproved() {
return showOnApproved;
}

public void setShowOnApproved(boolean showOnApproved) {
this.showOnApproved = showOnApproved;
}

@Column(nullable = false, columnDefinition = "boolean default true")
public boolean isShowWhenWorkflowDisabled() {
return showWhenWorkflowDisabled;
}

public void setShowWhenWorkflowDisabled(boolean showWhenWorkflowDisabled) {
this.showWhenWorkflowDisabled = showWhenWorkflowDisabled;
}

/**
* Get all the page's groups.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public class PageProperties implements Serializable {
private String icon;
private Page.PageFormat format;
private List<String> groups;
private boolean showOnNonApproved = true;
private boolean showOnApproved = true;
private boolean showWhenWorkflowDisabled = true;
private Page page;

public PageProperties() {
Expand All @@ -40,6 +43,9 @@ public PageProperties(Page p) {
status = p.getStatus();
label = p.getLabel();
icon = p.getIcon();
showOnNonApproved = p.isShowOnNonApproved();
showOnApproved = p.isShowOnApproved();
showWhenWorkflowDisabled = p.isShowWhenWorkflowDisabled();
if (CollectionUtils.isNotEmpty(p.getGroups())) {
groups = new ArrayList<>();
for (Group g : p.getGroups()) {
Expand Down Expand Up @@ -132,4 +138,28 @@ public List<String> getGroups() {
public void setGroups(List<String> groups) {
this.groups = groups;
}

public boolean isShowOnNonApproved() {
return showOnNonApproved;
}

public void setShowOnNonApproved(boolean showOnNonApproved) {
this.showOnNonApproved = showOnNonApproved;
}

public boolean isShowOnApproved() {
return showOnApproved;
}

public void setShowOnApproved(boolean showOnApproved) {
this.showOnApproved = showOnApproved;
}

public boolean isShowWhenWorkflowDisabled() {
return showWhenWorkflowDisabled;
}

public void setShowWhenWorkflowDisabled(boolean showWhenWorkflowDisabled) {
this.showWhenWorkflowDisabled = showWhenWorkflowDisabled;
}
}
21 changes: 21 additions & 0 deletions services/src/main/java/org/fao/geonet/api/pages/PagesAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.fao.geonet.domain.UserGroup;
import org.fao.geonet.domain.page.Page;
import org.fao.geonet.domain.page.PageIdentity;
import org.fao.geonet.languages.LocaleMessages;
import org.fao.geonet.repository.GroupRepository;
import org.fao.geonet.repository.UserGroupRepository;
import org.fao.geonet.repository.page.PageRepository;
Expand Down Expand Up @@ -83,6 +84,7 @@ public class PagesAPI {
private static final String PAGE_DELETED = "Page removed";
private static final String ERROR_FILE = "File not valid";
private static final String ERROR_CREATE = "Wrong parameters are provided";
private static final String RESERVED_STATIC_PAGE_PREFIX = "gn-";

private final PageRepository pageRepository;
private final GroupRepository groupRepository;
Expand Down Expand Up @@ -178,6 +180,15 @@ private ResponseEntity<String> createPage(PageProperties pageProperties,
checkValidLanguage(language);
}

if (pageId != null && pageId.startsWith(RESERVED_STATIC_PAGE_PREFIX)) {
throw new IllegalArgumentException(
LocaleMessages.getMessageForLocale(
"api.exception.invalidStaticPageIdReservedPrefix",
new String[]{pageId, RESERVED_STATIC_PAGE_PREFIX},
null,
"apiMessages"));
}

if (!StringUtils.isBlank(link)) {
if (EmailUtil.isValidEmailAddress(link)) {
format = Page.PageFormat.EMAILLINK;
Expand Down Expand Up @@ -213,6 +224,10 @@ private ResponseEntity<String> createPage(PageProperties pageProperties,
}
}

newPage.setShowOnNonApproved(pageProperties.isShowOnNonApproved());
newPage.setShowOnApproved(pageProperties.isShowOnApproved());
newPage.setShowWhenWorkflowDisabled(pageProperties.isShowWhenWorkflowDisabled());

pageRepository.save(newPage);
return ResponseEntity.status(HttpStatus.CREATED).body("{}");
} else {
Expand Down Expand Up @@ -296,6 +311,9 @@ private ResponseEntity<Void> updatePageInternal(@NotNull String language,
newIcon != null ? newIcon : pageToUpdate.getIcon(),
CollectionUtils.isNotEmpty(_groups)? _groups: null);

pageCopy.setShowOnNonApproved(pageProperties.isShowOnNonApproved());
pageCopy.setShowOnApproved(pageProperties.isShowOnApproved());
pageCopy.setShowWhenWorkflowDisabled(pageProperties.isShowWhenWorkflowDisabled());
pageRepository.save(pageCopy);
pageRepository.delete(pageToUpdate);
} else {
Expand All @@ -305,6 +323,9 @@ private ResponseEntity<Void> updatePageInternal(@NotNull String language,
pageToUpdate.setStatus(pageProperties.getStatus() != null ? pageProperties.getStatus() : pageToUpdate.getStatus());
pageToUpdate.setLabel(newLabel);
pageToUpdate.setIcon(newIcon);
pageToUpdate.setShowOnNonApproved(pageProperties.isShowOnNonApproved());
pageToUpdate.setShowOnApproved(pageProperties.isShowOnApproved());
pageToUpdate.setShowWhenWorkflowDisabled(pageProperties.isShowWhenWorkflowDisabled());

pageToUpdate.getGroups().clear();
if (pageToUpdate.getStatus() == Page.PageStatus.GROUPS || pageToUpdate.getStatus() == Page.PageStatus.GROUPS_AND_ADMIN) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@
}
]);

/**
* Render static page menu entries for a configured section.
*
* Accepts a page id, an array of page ids, or nested submenu config through
* `gn-static-page-menu`.
*/
module.directive("gnStaticPageMenu", [
"gnStaticPagesService",
"gnGlobalSettings",
Expand Down Expand Up @@ -140,6 +146,120 @@
// Set button style based on explicit parameter (defaults to false)
$scope.renderAsButton = $scope.renderAsButton === "true";

/**
* Apply runtime visibility rules for a page.
*
* In `record_view_menu`, visibility depends on workflow state and
* metadata approval status from the current record context.
*/
function shouldShowPage(page) {
if (!page) return false;
if ($scope.section === "record_view_menu" && $scope.context) {
if (!$scope.context.isWorkflowEnabled)
return page.showWhenWorkflowDisabled === true;
if ($scope.context.mdStatus == 2) return page.showOnApproved === true;
return page.showOnNonApproved === true;
}
return true;
}

/**
* Recursively build a display-ready menu from `pagesConfig`.
*
* `pagesConfig` may contain page ids (strings) and submenu descriptors.
* Unknown page ids are ignored and logged to help diagnose UI config issues.
*/
function buildMenu(pagesMenu, staticPages, pagesConfig) {
pagesConfig.forEach(function (menu) {
if (typeof menu === "string") {
if (staticPages[menu]) {
if (shouldShowPage(staticPages[menu])) {
pagesMenu.push(staticPages[menu]);
}
} else {
console.warn(
menu +
" not found in pages configuration." +
" Check your UI configuration."
);
}
} else if (angular.isObject(menu)) {
var key = Object.keys(menu)[0];
var value = menu[key];

var submenu = {
label: key,
icon: undefined,
type: "submenu",
pages: []
};

var menuItems;

// If the submenu is using the legacy array format
if (angular.isArray(value)) {
menuItems = value;
// If the submenu is using the new object format with an items array and optional icon
} else if (angular.isObject(value)) {
if (angular.isArray(value.items)) {
menuItems = value.items;
}
if (angular.isDefined(value.icon)) {
submenu.icon = value.icon;
}
} else {
console.warn(
"Invalid menu configuration for " +
key +
". Expected an array of page identifiers or an object with an items array."
);
return;
}

buildMenu(submenu.pages, staticPages, menuItems);

if (submenu.pages.length > 0) {
pagesMenu.push(submenu);
}
}
});
return pagesMenu;
}

/**
* Recompute rendered menu items from loaded pages and current config.
*
* When a single item is produced, expose it as `$scope.page` for
* simplified template handling; otherwise keep list rendering mode.
*/
function rebuildMenu() {
if (!$scope.pages) {
return;
}

$scope.pagesMenu = [];
buildMenu($scope.pagesMenu, $scope.pages, $scope.pagesConfig);

if ($scope.pagesMenu.length === 1) {
$scope.page = $scope.pagesMenu[0];
$scope.isSubmenu = $scope.page.type === "submenu";
$scope.isExternalLink =
$scope.page.format === "LINK" ||
$scope.page.format === "EMAILLINK" ||
$scope.page.format === "HTMLPAGE";

if (
$scope.page.format === "EMAILLINK" &&
$scope.page.link &&
!$scope.page.link.startsWith("mailto:")
) {
$scope.page.link = "mailto:" + $scope.page.link;
}
} else {
$scope.page = null;
}
}

if ($scope.pagesConfig.length > 0) {
gnStaticPagesService.loadPages($scope.language, $scope.section).then(
function (response) {
Expand All @@ -149,28 +269,21 @@
$scope.pages[page.pageId] = page;
});

gnStaticPagesService.buildMenu(
$scope.pagesMenu,
$scope.pages,
$scope.pagesConfig
);
if ($scope.pagesMenu.length === 1) {
$scope.page = $scope.pagesMenu[0];
$scope.isSubmenu = $scope.page.type === "submenu";
$scope.isExternalLink =
$scope.page.format === "LINK" ||
$scope.page.format === "EMAILLINK" ||
$scope.page.format === "HTMLPAGE";

if ($scope.page.format === "EMAILLINK") {
$scope.page.link = "mailto:" + $scope.page.link;
}
}
rebuildMenu();
},
function (response) {
$scope.pagesList = null;
}
);

// Rebuild menu when any context field changes.
$scope.$watch(
"context",
function () {
rebuildMenu();
},
true
);
}
}
};
Expand Down
Loading
Loading