/**
* Copyright © ${project.inceptionYear} Instituto Superior Técnico
*
* This file is part of Fenix IST.
*
* Fenix IST 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.
*
* Fenix IST 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 Fenix IST. If not, see <http://www.gnu.org/licenses/>.
*/
package pt.ist.fenix.ui.spring;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.fenixedu.bennu.core.groups.AnyoneGroup;
import org.fenixedu.bennu.core.groups.Group;
import org.fenixedu.bennu.core.groups.LoggedGroup;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.bennu.io.domain.GroupBasedFile;
import org.fenixedu.bennu.io.servlets.FileDownloadServlet;
import org.fenixedu.cms.domain.*;
import org.fenixedu.cms.domain.component.Component;
import org.fenixedu.cms.domain.component.StaticPost;
import org.fenixedu.commons.i18n.I18N;
import org.fenixedu.commons.i18n.LocalizedString;
import org.fenixedu.learning.domain.executionCourse.ExecutionCourseSite;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import pt.ist.fenix.domain.homepage.HomepageSite;
import pt.ist.fenixframework.Atomic;
import pt.ist.fenixframework.Atomic.TxMode;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Comparator.comparing;
@Service
public class PagesAdminService {
private final Predicate<MenuItem> isStaticPage =
menuItem -> menuItem.getPage() != null && menuItem.getPage().getComponentsSet().stream()
.filter(StaticPost.class::isInstance).map(component -> ((StaticPost) component).getPost())
.filter(post -> post != null).findFirst().isPresent();
protected static Stream<Page> dynamicPages(Site site) {
return site.getPagesSet().stream().filter(PagesAdminService::isDynamicPage)
.filter(page -> !site.getInitialPage().equals(page)).sorted(comparing(Page::getName));
}
protected static boolean isDynamicPage(Page page) {
return !page.getComponentsSet().stream().filter(StaticPost.class::isInstance).findAny().isPresent();
}
static List<Group> permissionGroups(Site site) {
if (site instanceof ExecutionCourseSite) {
return ((ExecutionCourseSite) site).getContextualPermissionGroups();
}
if (site instanceof HomepageSite) {
return ((HomepageSite) site).getContextualPermissionGroups();
}
return ImmutableList.of(AnyoneGroup.get(), LoggedGroup.get());
}
@Atomic(mode = Atomic.TxMode.WRITE)
protected void delete(MenuItem menuItem) {
//recursive call to remove associated childrens
menuItem.getChildrenSorted().forEach(this::delete);
//deleting a page allready deletes all the associated menu items and components
menuItem.getPage().delete();
}
@Atomic(mode = Atomic.TxMode.WRITE)
protected Optional<MenuItem> create(Site site, MenuItem parent, LocalizedString name, LocalizedString body) {
Menu menu = site.getMenusSet().stream().findFirst().orElse(null);
Page page = Page.create(site, menu, parent, Post.sanitize(name), true, "view", Authenticate.getUser());
Category category = site.getOrCreateCategoryForSlug("content", new LocalizedString().with(I18N.getLocale(), "Content"));
Post post = Post.create(site, page, Post.sanitize(name), Post.sanitize(body), category, true, Authenticate.getUser());
page.addComponents(new StaticPost(post));
MenuItem menuItem = page.getMenuItemsSet().stream().findFirst().get();
if (parent != null) {
parent.add(menuItem);
} else {
menu.add(menuItem);
}
return Optional.of(menuItem);
}
@Atomic(mode = Atomic.TxMode.WRITE)
protected MenuItem edit(MenuItem menuItem, LocalizedString name, LocalizedString body, Group canViewGroup, Boolean visible) {
name = Post.sanitize(name);
body = Post.sanitize(body);
if (!menuItem.getName().equals(name)) {
menuItem.setName(name);
}
Post post = postForPage(menuItem.getPage());
if (visible != null) {
menuItem.getPage().setPublished(visible);
}
if (!menuItem.getPage().getName().equals(name)) {
menuItem.getPage().setName(name);
}
if (post.getBody() == null && body != null || post.getBody() != null && !post.getBody().equals(body)) {
post.setBody(body);
}
if (!post.getName().equals(name)) {
post.setName(name);
}
if (canViewGroup != null && !post.getCanViewGroup().equals(canViewGroup)) {
post.setCanViewGroup(canViewGroup);
}
return menuItem;
}
@Atomic(mode = TxMode.WRITE)
protected void moveTo(MenuItem item, MenuItem parent, MenuItem insertAfter) {
Menu menu = item.getMenu();
if (insertAfter == null && parent == null) {
insertAfter = getLastBuiltinContent(menu);
}
if (parent == null) {
MenuItem.fixOrder(menu.getToplevelItemsSorted().collect(Collectors.toList()));
int newPosition = insertAfter == null ? 0 : insertAfter.getPosition() + 1;
menu.putAt(item, newPosition);
} else {
MenuItem.fixOrder(parent.getChildrenSorted());
int newPosition = insertAfter == null ? 0 : insertAfter.getPosition() + 1;
parent.putAt(item, newPosition);
}
}
private MenuItem getLastBuiltinContent(Menu menu) {
return menu.getToplevelItemsSorted().sorted(Comparator.reverseOrder()).filter(isStaticPage.negate()).findFirst()
.orElse(null);
}
@Atomic(mode = Atomic.TxMode.WRITE)
protected GroupBasedFile addAttachment(String name, MultipartFile attachment, MenuItem menuItem) throws IOException {
Post post = postForPage(menuItem.getPage());
GroupBasedFile file =
new GroupBasedFile(name, attachment.getOriginalFilename(), attachment.getBytes(), AnyoneGroup.get());
post.getAttachments().putFile(file, 0);
return file;
}
private Post postForPage(Page page) {
return page.getComponentsSet().stream().filter(component -> component instanceof StaticPost)
.map(component -> ((StaticPost) component).getPost()).filter(post -> post != null).findFirst().get();
}
protected JsonObject serialize(Site site) {
JsonObject data = new JsonObject();
if (!site.getMenusSet().isEmpty()) {
Menu menu = site.getMenusSet().stream().findFirst().get();
JsonObject root = new JsonObject();
root.add("title", site.getName().json());
root.add("root", new JsonPrimitive(true));
root.add("isFolder", new JsonPrimitive(true));
root.add("expanded", new JsonPrimitive(true));
root.add("key", new JsonPrimitive("null"));
JsonArray groupsJson = new JsonArray();
for (Group group : permissionGroups(site)) {
groupsJson.add(serializeGroup(group));
}
JsonArray child = new JsonArray();
menu.getToplevelItemsSorted().filter(isStaticPage).map(item -> serialize(item, false))
.forEach(json -> child.add(json));
root.add("children", child);
data.add("root", root);
data.add("groups", groupsJson);
}
return data;
}
protected JsonObject serialize(MenuItem item, boolean withBody) {
JsonObject root = new JsonObject();
root.add("title", item.getName().json());
if (item.getParent() != null) {
root.add("menuItemParentId", new JsonPrimitive(item.getParent().getExternalId()));
}
root.add("key", new JsonPrimitive(item.getExternalId()));
String pageAddress = Optional.ofNullable(item.getUrl()).orElse(item.getPage().getAddress());
root.add("pageAddress", new JsonPrimitive(pageAddress));
root.add("position", new JsonPrimitive(item.getPosition()));
root.add("isFolder", new JsonPrimitive(Optional.ofNullable(item.getFolder()).orElse(false)));
root.addProperty("visible", item.getPage().isPublished());
if (withBody) {
root.add("body", data(item.getMenu().getSite(), item));
}
root.add("files", serializeAttachments(item.getPage()));
if (item.getChildrenSet().size() > 0) {
root.add("folder", new JsonPrimitive(true));
JsonArray children = new JsonArray();
item.getChildrenSorted().stream().filter(isStaticPage).forEach(subitem -> children.add(serialize(subitem, false)));
root.add("children", children);
}
root.addProperty("canViewGroupIndex", canViewGroupIndex(item.getPage(), postForPage(item.getPage()).getCanViewGroup()));
return root;
}
private Integer canViewGroupIndex(Page page, Group group) {
List<Group> permissionGroups = permissionGroups(page.getSite());
for (int i = 0; i < permissionGroups.size(); ++i) {
if (permissionGroups.get(i).equals(group)) {
return i;
}
}
return 0;
}
private JsonObject serializeGroup(Group group) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", group.getPresentationName());
jsonObject.addProperty("expression", group.getExpression());
return jsonObject;
}
protected JsonElement serializeAttachments(Page page) {
Post post = postForPage(page);
JsonArray filesJson = new JsonArray();
for (GroupBasedFile postFile : post.getAttachments().getFiles()) {
JsonObject json = describeFile(page, postFile);
json.addProperty("visible", true);
filesJson.add(json);
}
if (filesJson.size() > 0) {
filesJson.get(filesJson.size() - 1).getAsJsonObject().addProperty("last", true);
}
for (GroupBasedFile postFile : post.getPostFiles().getFiles()) {
JsonObject json = describeFile(page, postFile);
json.addProperty("visible", false);
filesJson.add(json);
}
return filesJson;
}
protected JsonObject describeFile(Page page, GroupBasedFile file) {
JsonObject postFileJson = new JsonObject();
postFileJson.addProperty("name", file.getDisplayName());
postFileJson.addProperty("filename", file.getFilename());
postFileJson.addProperty("externalId", file.getExternalId());
postFileJson.addProperty("creationDate", file.getCreationDate().toString());
postFileJson.addProperty("contentType", file.getContentType());
postFileJson.addProperty("size", file.getSize());
postFileJson.addProperty("downloadUrl", FileDownloadServlet.getDownloadUrl(file));
postFileJson.addProperty("group", canViewGroupIndex(page, file.getAccessGroup()));
return postFileJson;
}
@Atomic
protected GroupBasedFile addPostFile(MultipartFile attachment, MenuItem menuItem) throws IOException {
GroupBasedFile f = new GroupBasedFile(attachment.getOriginalFilename(), attachment.getOriginalFilename(),
attachment.getBytes(), AnyoneGroup.get());
postForPage(menuItem.getPage()).getPostFiles().putFile(f);
return f;
}
@Atomic(mode = Atomic.TxMode.WRITE)
public void delete(MenuItem menuItem, GroupBasedFile file) {
Post post = postForPage(menuItem.getPage());
Post.Attachments attachments = post.getAttachments();
int attachmentPosition = attachments.getFiles().indexOf(file);
if(attachmentPosition != -1) {
attachments.removeFile(attachmentPosition);
file.delete();
} else if (post.getPostFiles().getFiles().indexOf(file) != -1) {
post.getPostFiles().removeFile(file);
file.delete();
}
}
@Atomic(mode = Atomic.TxMode.WRITE)
public void updateAttachment(MenuItem menuItem, GroupBasedFile attachment, int newPosition, int groupPosition,
String displayName, boolean visible) {
if (displayName != null) {
attachment.setDisplayName(displayName);
}
attachment.setAccessGroup(permissionGroups(menuItem.getMenu().getSite()).get(groupPosition));
Post post = postForPage(menuItem.getPage());
if (visible) {
Post.Attachments attachments = post.getAttachments();
if (attachment.getPostFile() != null) {
int currentPosition = attachments.getFiles().indexOf(attachment);
if (currentPosition != newPosition) {
attachments.move(currentPosition, newPosition);
}
} else {
attachment.setPost(null);
PostFile postFile = new PostFile();
postFile.setIndex(attachments.getFiles().size() + 1);
postFile.setPost(post);
postFile.setFiles(attachment);
}
} else {
if (attachment.getPostFile() != null) {
attachment.getPostFile().delete();
}
attachment.setPost(post);
}
}
protected void copyStaticPage(MenuItem oldMenuItem, ExecutionCourseSite newSite, Menu newMenu, MenuItem newParent) {
if(oldMenuItem.getPage() != null) {
Page oldPage = oldMenuItem.getPage();
staticPost(oldPage).ifPresent(oldPost -> {
Page newPage = new Page(newSite);
newPage.setName(oldPage.getName());
newPage.setTemplate(newSite.getTheme().templateForType(oldPage.getTemplate().getType()));
newPage.setCreatedBy(Authenticate.getUser());
newPage.setPublished(false);
for(Component component : oldPage.getComponentsSet()) {
if(component instanceof StaticPost) {
StaticPost staticPostComponent = (StaticPost) component;
Post newPost = clonePost(staticPostComponent.getPost(), newSite);
newPost.setActive(true);
StaticPost newComponent = new StaticPost(newPost);
newPage.addComponents(newComponent);
}
}
MenuItem newMenuItem = MenuItem.create(newMenu, newPage, oldMenuItem.getName(), newParent);
newMenuItem.setPosition(oldMenuItem.getPosition());
newMenuItem.setUrl(oldMenuItem.getUrl());
newMenuItem.setFolder(oldMenuItem.getFolder());
oldMenuItem.getChildrenSet().stream().forEach(child->copyStaticPage(child, newSite, newMenu, newMenuItem));
});
}
}
private Post clonePost(Post oldPost, Site newSite) {
Post newPost = new Post(newSite);
newPost.setName(oldPost.getName());
newPost.setBody(oldPost.getBody());
newPost.setCreationDate(new DateTime());
newPost.setCreatedBy(Authenticate.getUser());
newPost.setActive(oldPost.getActive());
for(Category oldCategory : oldPost.getCategoriesSet()) {
Category newCategory = newSite.getOrCreateCategoryForSlug(oldCategory.getSlug(), oldCategory.getName());
newPost.addCategories(newCategory);
}
for (int i = 0; i < oldPost.getAttachments().getFiles().size(); ++i) {
GroupBasedFile file = oldPost.getAttachments().getFiles().get(i);
GroupBasedFile attachmentCopy =
new GroupBasedFile(file.getDisplayName(), file.getFilename(), file.getContent(), AnyoneGroup.get());
newPost.getAttachments().putFile(attachmentCopy, i);
}
for (GroupBasedFile file : oldPost.getPostFiles().getFiles()) {
GroupBasedFile postFileCopy =
new GroupBasedFile(file.getDisplayName(), file.getFilename(), file.getContent(), AnyoneGroup.get());
newPost.getPostFiles().putFile(postFileCopy);
}
return newPost;
}
private Optional<Post> staticPost(Page page) {
return page.getComponentsSet().stream().filter(StaticPost.class::isInstance).map(StaticPost.class::cast)
.map(StaticPost::getPost).findFirst();
}
public JsonElement data(Site site, MenuItem item) {
return postForPage(item.getPage()).getBody() != null ? postForPage(item.getPage()).getBody().json() : new JsonObject();
}
}