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 @@ -218,6 +218,31 @@ public LayoutModel updatePageLayout(
}
}

@PatchMapping(value = "restore", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@Secured("users")
@Operation(summary = "Restore a page's default layout", method = "PATCH", description = "This restores the designated page's layout to its shipped default")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Request fulfilled"),
@ApiResponse(responseCode = "400", description = "Invalid request input"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "404", description = "Not found"),
})
public void restorePageLayout(
HttpServletRequest request,
@Parameter(description = "page display name", required = true)
@RequestParam("pageRef")
String pageRef) {
try {
pageLayoutService.restorePageLayout(PageKey.parse(pageRef), request.getRemoteUser());
} catch (IllegalStateException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
} catch (ObjectNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
} catch (IllegalAccessException e) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, e.getMessage());
}
}

@PatchMapping(name = "link", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@Secured("users")
@Operation(summary = "Update page link", method = "GET", description = "This updates page link")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import org.exoplatform.commons.ObjectAlreadyExistsException;
import org.exoplatform.commons.exception.ObjectNotFoundException;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.ModelObject;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
Expand Down Expand Up @@ -76,6 +77,9 @@ public class SiteLayoutRest {
@Autowired
private LayoutService layoutService;

@Autowired
private UserPortalConfigService portalConfigService;

@GetMapping("{siteId}")
@Operation(summary = "Gets a specific site by its id", description = "Gets site by id", method = "GET")
@ApiResponses(value = {
Expand Down Expand Up @@ -104,7 +108,8 @@ public ResponseEntity<SiteEntity> getSiteById(
}
SiteEntity siteEntity = RestEntityBuilder.toSiteEntity(site,
request,
locale);
locale,
portalConfigService);
String eTag = String.valueOf(siteEntity.hashCode());
if (webRequest.checkNotModified(eTag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
Expand Down Expand Up @@ -321,7 +326,8 @@ public ResponseEntity<SiteEntity> createSite(
request.getRemoteUser());
SiteEntity siteEntity = RestEntityBuilder.toSiteEntity(site,
request,
request.getLocale());
request.getLocale(),
portalConfigService);
return ResponseEntity.ok()
.body(siteEntity);
} catch (ObjectAlreadyExistsException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.Application;
import org.exoplatform.portal.config.model.Container;
import org.exoplatform.portal.config.model.ModelObject;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.rest.model.UserNodeRestEntity;
import org.exoplatform.portal.mop.service.LayoutService;
import org.exoplatform.social.rest.api.EntityBuilder;
import org.exoplatform.social.rest.entity.SiteEntity;
Expand All @@ -47,15 +49,37 @@ private RestEntityBuilder() {

public static SiteEntity toSiteEntity(PortalConfig site,
HttpServletRequest request,
Locale locale) throws Exception {
return EntityBuilder.buildSiteEntity(site,
request,
true,
null,
false,
false,
false,
locale);
Locale locale,
UserPortalConfigService portalConfigService) throws Exception {
SiteEntity siteEntity = EntityBuilder.buildSiteEntity(site,
request,
true,
null,
false,
false,
false,
locale);
if (siteEntity != null) {
markRestorableNodes(siteEntity.getSiteNavigations(), portalConfigService);
}
return siteEntity;
}

/**
* Flags each navigation node whose page is a default/product page, so the UI can offer to restore
* the page's layout to its shipped default only where it makes sense.
*/
private static void markRestorableNodes(List<UserNodeRestEntity> nodes,
UserPortalConfigService portalConfigService) {
if (CollectionUtils.isEmpty(nodes)) {
return;
}
for (UserNodeRestEntity node : nodes) {
if (node.getPageKey() != null) {
node.setCanRestoreLayout(portalConfigService.isDefaultPage(node.getPageKey()));
}
markRestorableNodes(node.getChildren(), portalConfigService);
}
}

public static LayoutModel toLayoutModel(ModelObject modelObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@

import org.exoplatform.commons.addons.AddOnService;
import org.exoplatform.commons.exception.ObjectNotFoundException;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.mop.PageType;
import org.exoplatform.portal.mop.QueryResult;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.Utils;
import org.exoplatform.portal.mop.importer.ImportMode;
import org.exoplatform.portal.mop.page.PageContext;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageState;
Expand Down Expand Up @@ -92,6 +94,9 @@ public class PageLayoutService {
@Autowired
private AddOnService addOnService;

@Autowired
private UserPortalConfigService portalConfigService;

public List<PageContext> getPages(String siteTypeName,
String siteName,
String pageDisplayName,
Expand Down Expand Up @@ -348,6 +353,40 @@ public PageContext updatePageLayout(String pageRef,
return layoutService.getPageContext(existingPage.getPageKey());
}

/**
* Restores a single page's layout to its shipped default, preserving the page's current access
* and edit permissions (only the layout is reset, not who can see/edit the page).
*
* @param pageKey the key of the page to restore
* @param username the user requesting the restore
* @return the restored {@link PageContext}
* @throws ObjectNotFoundException when the page doesn't exist
* @throws IllegalAccessException when the user can't edit the page's layout
* @throws IllegalStateException when the page isn't part of any default/product configuration
*/
public PageContext restorePageLayout(PageKey pageKey, String username) throws ObjectNotFoundException,
IllegalAccessException {
PageContext existingPageContext = layoutService.getPageContext(pageKey);
if (existingPageContext == null) {
throw new ObjectNotFoundException(String.format(PAGE_NOT_EXISTS_MESSAGE, pageKey.format()));
} else if (!aclService.canEditPage(pageKey, username)) {
throw new IllegalAccessException(String.format(PAGE_NOT_EDITABLE_MESSAGE, pageKey.format(), username));
}
PageState previousState = existingPageContext.getState();
boolean restored = portalConfigService.restorePage(pageKey, ImportMode.RESTORE_DEFAULTS);
if (!restored) {
throw new IllegalStateException(String.format("Page %s can't be restored since it isn't a default page",
pageKey.format()));
}
PageContext restoredPageContext = layoutService.getPageContext(pageKey);
PageState restoredState = restoredPageContext.getState();
restoredState.setAccessPermissions(previousState.getAccessPermissions());
restoredState.setEditPermission(previousState.getEditPermission());
restoredPageContext.setState(restoredState);
layoutService.save(restoredPageContext);
return restoredPageContext;
}

public void updatePageLink(PageKey pageKey,
String link,
String username) throws ObjectNotFoundException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

import org.exoplatform.commons.ObjectAlreadyExistsException;
import org.exoplatform.commons.exception.ObjectNotFoundException;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.ModelObject;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
Expand Down Expand Up @@ -108,6 +109,9 @@ public class SiteLayoutRestTest {
@MockBean
private LayoutService layoutService;

@MockBean
private UserPortalConfigService portalConfigService;

@Autowired
private SecurityFilterChain filterChain;

Expand Down Expand Up @@ -147,7 +151,7 @@ void getSiteById() throws Exception {
PortalConfig site = mock(PortalConfig.class);
when(siteLayoutService.getSite(2l, SIMPLE_USER)).thenReturn(site);
try (MockedStatic<RestEntityBuilder> restEntityBuilder = mockStatic(RestEntityBuilder.class)) {
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any())).thenReturn(mock(SiteEntity.class));
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any(), any())).thenReturn(mock(SiteEntity.class));
ResultActions response = mockMvc.perform(get(REST_PATH + "/2").with(testSimpleUser()));
response.andExpect(status().isOk());
}
Expand Down Expand Up @@ -176,7 +180,7 @@ void getSite() throws Exception {
when(site.getId()).thenReturn(2l);
when(siteLayoutService.getSite(2l, SIMPLE_USER)).thenReturn(site);
try (MockedStatic<RestEntityBuilder> restEntityBuilder = mockStatic(RestEntityBuilder.class)) {
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any())).thenReturn(mock(SiteEntity.class));
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any(), any())).thenReturn(mock(SiteEntity.class));
ResultActions response = mockMvc.perform(get(GET_SITE_REST_PATH).with(testSimpleUser()));
response.andExpect(status().isOk());
}
Expand Down Expand Up @@ -371,7 +375,7 @@ void createDraftSite() {
when(siteLayoutService.getSite(draftSiteKey, SIMPLE_USER)).thenReturn(draftSite);
when(siteLayoutService.createDraftSite(SITE_KEY, SIMPLE_USER)).thenReturn(draftSiteKey);
try (MockedStatic<RestEntityBuilder> restEntityBuilder = mockStatic(RestEntityBuilder.class)) {
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any())).thenReturn(mock(SiteEntity.class));
restEntityBuilder.when(() -> RestEntityBuilder.toSiteEntity(any(), any(), any(), any())).thenReturn(mock(SiteEntity.class));
ResultActions response = mockMvc.perform(post(CREATE_DRAFT_LAYOUT_REST_PATH).with(testSimpleUser()));
response.andExpect(status().isOk());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.exoplatform.portal.mop.PageType;
import org.exoplatform.portal.mop.QueryResult;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.importer.ImportMode;
import org.exoplatform.portal.mop.page.PageContext;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageState;
Expand Down Expand Up @@ -484,6 +485,31 @@ public void updatePageLayout() throws IllegalAccessException, ObjectNotFoundExce
assertDoesNotThrow(() -> pageLayoutService.updatePageLayout(PAGE_KEY.format(), page, true, TEST_USER));
}

@Test
public void restorePageLayout() throws IllegalAccessException, ObjectNotFoundException {
assertThrows(ObjectNotFoundException.class, () -> pageLayoutService.restorePageLayout(PAGE_KEY, TEST_USER));
when(layoutService.getPageContext(PAGE_KEY)).thenReturn(pageContext);
assertThrows(IllegalAccessException.class, () -> pageLayoutService.restorePageLayout(PAGE_KEY, TEST_USER));
when(aclService.canEditPage(PAGE_KEY, TEST_USER)).thenReturn(true);
when(pageContext.getState()).thenReturn(pageState);

assertThrows(IllegalStateException.class, () -> pageLayoutService.restorePageLayout(PAGE_KEY, TEST_USER));
verify(userPortalConfigService).restorePage(PAGE_KEY, ImportMode.RESTORE_DEFAULTS);

when(userPortalConfigService.restorePage(PAGE_KEY, ImportMode.RESTORE_DEFAULTS)).thenReturn(true);
List<String> accessPermissions = Arrays.asList("access", "permissions");
String editPermission = "edit permission";
when(pageState.getAccessPermissions()).thenReturn(accessPermissions);
when(pageState.getEditPermission()).thenReturn(editPermission);

PageContext result = pageLayoutService.restorePageLayout(PAGE_KEY, TEST_USER);
assertEquals(pageContext, result);
verify(pageState).setAccessPermissions(accessPermissions);
verify(pageState).setEditPermission(editPermission);
verify(pageContext).setState(pageState);
verify(layoutService).save(pageContext);
}

@Test
public void updatePageLink() throws IllegalAccessException, IllegalStateException, ObjectNotFoundException {
String link = "link";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ siteNavigation.button.tooltip.label=Site navigation
siteNavigation.drawer.title=Site navigation
siteNavigation.label.btn.createNode=Add
siteNavigation.label.editLayout=Edit Layout
siteNavigation.label.restoreLayout=Restore Layout
siteNavigation.label.restoreLayoutSuccess=Page layout has been restored to its default
siteNavigation.label.restoreLayoutError=Error while restoring the page layout
siteNavigation.label.copyLink=Copy link
siteNavigation.label.pageUrlCopiedSuccessfully=Page link is copied
siteNavigation.label.pageUrlCopiedError=Error while copying the page URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,25 @@
<div class="d-contents">
<tr>
<td>
<v-hover :disabled="$root.mobileDisplayMode">
<v-hover v-model="hover" :disabled="$root.mobileDisplayMode">
<v-row
slot-scope="{ hover }"
class="d-flex pt-2 px-0 text-truncate v-list-item v-list-item--dense d-flex flex-nowrap"
:class="extraClass">
:class="extraClass"
tabindex="0"
@focusin="rowFocused = true"
@focusout="rowFocused = $event.currentTarget.contains($event.relatedTarget)">
<v-col
:cols="cols"
class="my-0 py-0 px-0">
<v-icon
<v-btn
v-if="hasChildren && !hideChildren"
size="23"
class="align-center px-0"
:aria-expanded="displayChildren ? 'true' : 'false'"
:class="!$vuetify.rtl ? 'pull-right' : 'pull-left'"
icon
small
@click="displayChildren = !displayChildren">
{{ icon }}
</v-icon>
<v-icon size="23" class="align-center px-0">{{ icon }}</v-icon>
</v-btn>
<div v-else class="ms-3 me-2"></div>
</v-col>
<v-col
Expand Down Expand Up @@ -69,13 +72,25 @@
</v-col>
<v-col
cols="2"
class="mb-1 mt-0 py-0">
class="mb-1 mt-0 py-0 d-flex align-center justify-end flex-nowrap">
<v-btn
v-show="showActions && canMoveUp"
:title="$t('siteNavigation.label.moveUp')"
icon
@click="moveUpNode">
<v-icon size="16" class="icon-default-color">fas fa-arrow-up</v-icon>
</v-btn>
<v-btn
v-show="showActions && canMoveDown"
:title="$t('siteNavigation.label.moveDown')"
icon
@click="moveDownNode">
<v-icon size="16" class="icon-default-color">fas fa-arrow-down</v-icon>
</v-btn>
<site-navigation-node-item-menu
:navigation-node="navigationNode"
:hover="hover"
:hover="showActions"
:can-delete="canDelete"
:can-move-up="canMoveUp"
:can-move-down="canMoveDown"
:node-to-paste="nodeToPaste"
:paste-mode="pasteMode" />
</v-col>
Expand Down Expand Up @@ -174,6 +189,8 @@ export default {
},
data() {
return {
hover: false,
rowFocused: false,
displayChildren: false,
nodeToPaste: null,
pasteMode: null,
Expand All @@ -191,6 +208,9 @@ export default {
highlightNode() {
return this.navigationNode.uri === eXo.env.portal.selectedNodeUri;
},
showActions() {
return this.hover || this.rowFocused;
},
icon() {
return this.displayChildren && 'mdi-menu-down' || 'mdi-menu-right';
},
Expand Down Expand Up @@ -263,6 +283,12 @@ export default {
});
},
methods: {
moveUpNode() {
this.$root.$emit('moveup-node', this.navigationNode.id);
},
moveDownNode() {
this.$root.$emit('movedown-node', this.navigationNode.id);
},
moveUpChildNode(navigationNodeId) {
if (this.navigationNode.children.length) {
const index = this.navigationNode?.children?.findIndex?.(navigationNode => navigationNode.id === navigationNodeId);
Expand Down
Loading
Loading