/**
* Copyright © 2014 Instituto Superior Técnico
*
* This file is part of FenixEdu CMS.
*
* FenixEdu CMS 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.
*
* FenixEdu CMS 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 FenixEdu CMS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fenixedu.cms.domain;
import static org.fenixedu.commons.i18n.LocalizedString.fromJson;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.bennu.signals.DomainObjectEvent;
import org.fenixedu.bennu.signals.Signal;
import org.fenixedu.cms.domain.wraps.Wrap;
import org.fenixedu.cms.domain.wraps.Wrappable;
import org.fenixedu.cms.exceptions.CmsDomainException;
import org.fenixedu.commons.i18n.LocalizedString;
import org.joda.time.DateTime;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import pt.ist.fenixframework.Atomic;
import pt.ist.fenixframework.consistencyPredicates.ConsistencyPredicate;
/**
* Models the items of a {@link Menu}
*/
public class MenuItem extends MenuItem_Base implements Comparable<MenuItem>, Wrappable, Cloneable {
public static final Comparator<MenuItem> CREATION_DATE_COMPARATOR = Comparator.comparing(MenuItem::getCreationDate);
public static final String SIGNAL_CREATED = "fenixedu.cms.menuItem.created";
public static final String SIGNAL_DELETED = "fenixedu.cms.menuItem.deleted";
public static final String SIGNAL_EDITED = "fenixedu.cms.menuItem.edited";
/**
* The logged {@link User} creates a new MenuItem.
* @param menu menu
*/
public MenuItem(Menu menu) {
super();
if (Authenticate.getUser() == null) {
throw CmsDomainException.forbiden();
}
this.setCreatedBy(Authenticate.getUser());
this.setCreationDate(new DateTime());
this.setFolder(false);
this.setMenu(menu);
Signal.emit(MenuItem.SIGNAL_CREATED, new DomainObjectEvent<MenuItem>(this));
}
@Override
public Menu getMenu() {
return super.getMenu();
}
/**
* Adds a children at a given position and shifts the existing items.
*
* @param item the {@link MenuItem} to be added.
* @param position the position where the item should be added.
*/
public void putAt(MenuItem item, int position) {
if (item.getPosition() != null) {
item.removeFromParent();
}
if (position < 0) {
position = 0;
} else if (position > getChildrenSet().size()) {
item.removeFromParent();
position = getChildrenSet().size();
}
List<MenuItem> list = Lists.newArrayList(getChildrenSorted());
list.add(position, item);
fixOrder(list);
item.setMenu(getMenu());
getChildrenSet().add(item);
}
/**
* Removes a given {@link MenuItem}
*
* @param mi the children to be removed
*/
public void remove(MenuItem mi) {
ArrayList<MenuItem> items = Lists.newArrayList(getChildrenSorted());
items.remove(mi);
fixOrder(items);
removeChildren(mi);
}
/**
* Adds a new {@link MenuItem} has the last item.
*
* @param mi the {@link MenuItem} to be added.
*/
public void add(MenuItem mi) {
this.putAt(mi, getChildrenSet().size());
}
/**
* Removes the {@link MenuItem} from its parent.
* <p>
* The Parent can be a {@link Menu} or a {@link MenuItem}
* </p>
*/
public void removeFromParent() {
if (getTop() != null) {
getTop().remove(this);
}
if (getParent() != null) {
getParent().remove(this);
}
setTop(null);
setParent(null);
}
/**
* @return the childrens sorted by position
*/
public List<MenuItem> getChildrenSorted() {
return getChildrenSet().stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
}
/**
* @return the URL address to visit the item.
*/
public String getAddress() {
if (getFolder()) {
return "#";
}
if (getUrl() != null) {
return getUrl();
} else {
return getPage().getAddress();
}
}
/**
* A MenuItem can not be linked with a {@link Menu} and a {@link MenuItem} at the same time
* @return if is parent or top
*/
@ConsistencyPredicate
public boolean parentOrTop() {
return !(getTop() != null && getParent() != null);
}
@Atomic
public void delete() {
Signal.emit(MenuItem.SIGNAL_CREATED, new DomainObjectEvent<>(this.getMenu()));
List<MenuItem> items = Lists.newArrayList(getChildrenSet());
removeFromParent();
items.forEach(i -> remove(i));
setCreatedBy(null);
setMenu(null);
setPage(null);
deleteDomainObject();
}
@Override
public int compareTo(MenuItem o) {
return getPosition() - o.getPosition();
}
@Override
public Integer getPosition() {
return Optional.ofNullable(super.getPosition()).orElse(0);
}
public static void fixOrder(List<MenuItem> sortedItems) {
for (int i = 0; i < sortedItems.size(); ++i) {
sortedItems.get(i).setPosition(i);
}
}
public static MenuItem create(Menu menu, Page page, LocalizedString name, MenuItem parent) {
MenuItem menuItem = new MenuItem(menu);
menuItem.setName(name);
menuItem.setPage(page);
menuItem.setFolder(page == null);
if (menu != null) {
if (parent != null) {
parent.add(menuItem);
} else {
menu.add(menuItem);
}
}
return menuItem;
}
@Override
public MenuItem clone(CloneCache cloneCache) {
return cloneCache.getOrClone(this, obj -> {
MenuItem clone = new MenuItem(null);
cloneCache.setClone(MenuItem.this, clone);
clone.setName(getName() != null ? fromJson(getName().json()) : null);
clone.setPosition(getPosition());
clone.setFolder(getFolder());
clone.setUrl(getUrl());
clone.setPage(getPage() != null ? getPage().clone(cloneCache) : null);
clone.setParent(getParent() != null ? getParent().clone(cloneCache) : null);
clone.setTop(getTop() != null ? getTop().clone(cloneCache) : null);
return clone;
});
}
public class MenuItemWrap extends Wrap {
private final boolean active;
private final boolean open;
private final List<Wrap> children;
public MenuItemWrap() {
children =
MenuItem.this.getChildrenSorted().stream().filter(MenuItem::isVisible).map((menuItem) -> menuItem.makeWrap())
.collect(Collectors.toList());
active = false;
open = false;
}
public MenuItemWrap(Page page) {
open = MenuItem.this.getPage() != null && MenuItem.this.getPage().equals(page);
children =
ImmutableList.copyOf(MenuItem.this.getChildrenSorted().stream().filter(MenuItem::isVisible)
.map(menuItem -> menuItem.makeWrap(page)).collect(Collectors.toList()));
active = open || children.stream().anyMatch(item -> ((MenuItemWrap) item).open || ((MenuItemWrap) item).active);
}
public List<Wrap> getChildren() {
return children;
}
public LocalizedString getName() {
return MenuItem.this.getName();
}
public String getAddress() {
return MenuItem.this.getAddress();
}
public boolean isActive() {
return active;
}
public boolean isOpen() {
return open;
}
public boolean isFolder() {
return MenuItem.this.getFolder();
}
public String getRssUrl() {
Page page = MenuItem.this.getPage();
return page == null ? null : page.getRssUrl();
}
}
@Override
public Wrap makeWrap() {
return new MenuItemWrap();
}
public Wrap makeWrap(Page page) {
return new MenuItemWrap(page);
}
public boolean isVisible() {
return getPage() == null || getPage().getPublished();
}
}