858 lines
29 KiB
Java
Executable File
858 lines
29 KiB
Java
Executable File
/*
|
|
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
|
|
*
|
|
* This library 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 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library 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 this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
package com.arsdigita.cms;
|
|
|
|
import com.arsdigita.auditing.BasicAuditTrail;
|
|
import com.arsdigita.cms.lifecycle.Lifecycle;
|
|
import com.arsdigita.cms.lifecycle.LifecycleDefinition;
|
|
import com.arsdigita.cms.util.SecurityConstants;
|
|
import com.arsdigita.domain.DataObjectNotFoundException;
|
|
import com.arsdigita.domain.DomainObjectFactory;
|
|
import com.arsdigita.domain.DomainCollectionIterator;
|
|
import com.arsdigita.kernel.ACSObject;
|
|
import com.arsdigita.kernel.Party;
|
|
import com.arsdigita.kernel.User;
|
|
import com.arsdigita.kernel.permissions.PermissionService;
|
|
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
|
|
import com.arsdigita.persistence.DataCollection;
|
|
import com.arsdigita.persistence.DataObject;
|
|
import com.arsdigita.persistence.DataQuery;
|
|
import com.arsdigita.persistence.DataQueryDataCollectionAdapter;
|
|
import com.arsdigita.persistence.OID;
|
|
import com.arsdigita.persistence.PersistenceException;
|
|
import com.arsdigita.persistence.Session;
|
|
import com.arsdigita.persistence.SessionManager;
|
|
import com.arsdigita.persistence.metadata.Property;
|
|
import com.arsdigita.util.Assert;
|
|
import com.arsdigita.util.UncheckedWrapperException;
|
|
import org.apache.log4j.Logger;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
|
|
/**
|
|
* This class represents folders for which to organize items in a tree hierarchy.
|
|
*
|
|
* Folders will only ever exist as draft or live versions. There should never be any folders that are pending. The
|
|
* pending versions of ordinary content items are stored in the live version of folders.
|
|
*
|
|
* Folders cannot have their own lifecycles. The methods to get or set lifecycles are no-ops.
|
|
*
|
|
* You should never call {@link #publish} or {@link #unpublish} on a folder; at present, these methods only log a
|
|
* warning when they are called. In the future, these warnings may be turned into actual errors.
|
|
*
|
|
* @author Jack Chung
|
|
* @author Michael Pih
|
|
* @author David Lutterkort
|
|
* @version $Id: Folder.java 1639 2007-09-17 13:20:13Z chrisg23 $
|
|
*/
|
|
public class Folder extends ContentItem {
|
|
|
|
private static final Logger s_log = Logger.getLogger(Folder.class);
|
|
public static final String BASE_DATA_OBJECT_TYPE =
|
|
"com.arsdigita.cms.Folder";
|
|
public static final String INDEX = "index";
|
|
public static final String HOME_FOLDER = "homeFolder";
|
|
public static final String HOME_SECTION = "homeSection";
|
|
private static final String ITEMS_QUERY = "com.arsdigita.cms.ItemsInFolder";
|
|
private static final String PRIMARY_INSTANCES_QUERY =
|
|
"com.arsdigita.cms.PrimaryInstancesInFolder";
|
|
private static final String ITEM_QUERY = "com.arsdigita.cms.ItemInFolder";
|
|
private static final String FOLDER_QUERY = "com.arsdigita.cms.FolderInFolder";
|
|
private static final String LABEL = "label";
|
|
private static final String NAME = "name";
|
|
private final static String ITEM = "item";
|
|
private boolean m_wasNew;
|
|
protected static final String ITEMS = "items";
|
|
|
|
/**
|
|
* Default constructor. This creates a new folder.
|
|
*/
|
|
public Folder() {
|
|
super(BASE_DATA_OBJECT_TYPE);
|
|
}
|
|
|
|
/**
|
|
* Constructor. The contained
|
|
* <code>DataObject</code> is retrieved from the persistent storage mechanism with an
|
|
* <code>OID</code> specified by <cod>oid</code>.
|
|
*
|
|
* @param oid The <code>OID</code> for the retrieved <code>DataObject</code>.
|
|
*/
|
|
public Folder(final OID oid) throws DataObjectNotFoundException {
|
|
super(oid);
|
|
}
|
|
|
|
/**
|
|
* Constructor. The contained
|
|
* <code>DataObject</code> is retrieved from the persistent storage mechanism with an
|
|
* <code>OID</code> specified by
|
|
* <code>id</code> and
|
|
* <code>Folder.BASE_DATA_OBJECT_TYPE</code>.
|
|
*
|
|
* @param id The <code>id</code> for the retrieved <code>DataObject</code>
|
|
*/
|
|
public Folder(final BigDecimal id) throws DataObjectNotFoundException {
|
|
this(new OID(BASE_DATA_OBJECT_TYPE, id));
|
|
}
|
|
|
|
public Folder(final DataObject obj) {
|
|
super(obj);
|
|
}
|
|
|
|
public Folder(final String type) {
|
|
super(type);
|
|
}
|
|
|
|
protected ContentItem makeCopy() {
|
|
final Folder newItem = (Folder) super.makeCopy();
|
|
DomainCollectionIterator items = new DomainCollectionIterator(getItems());
|
|
newItem.copyItemsToFolder(items);
|
|
return newItem;
|
|
}
|
|
|
|
public void copyItemsToFolder(final Iterator items) {
|
|
while (items.hasNext()) {
|
|
ContentItem item = (ContentItem) items.next();
|
|
item.copy(this, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return the base PDL object type for this item. Child classes should override this method to return the correct
|
|
* value
|
|
*/
|
|
public String getBaseDataObjectType() {
|
|
return BASE_DATA_OBJECT_TYPE;
|
|
}
|
|
|
|
/**
|
|
* Deletes the folder.
|
|
*
|
|
* @throws IllegalStateException if the folder is not empty.
|
|
*/
|
|
public void delete() throws IllegalStateException {
|
|
s_log.debug("Deleting folder");
|
|
|
|
if (!isEmpty()) {
|
|
throw new IllegalStateException("Attempt to delete non-empty folder " + getOID() + "; "
|
|
+ "only empty folders can be deleted");
|
|
}
|
|
|
|
super.delete();
|
|
}
|
|
|
|
protected void beforeDelete() {
|
|
DataCollection maps = SessionManager.getSession().retrieve(UserHomeFolderMap.BASE_DATA_OBJECT_TYPE);
|
|
maps.addEqualsFilter(HOME_FOLDER + "." + ID, getID());
|
|
while (maps.next()) {
|
|
maps.getDataObject().delete();
|
|
}
|
|
|
|
super.beforeDelete();
|
|
}
|
|
|
|
protected void beforeSave() {
|
|
m_wasNew = isNew();
|
|
|
|
super.beforeSave();
|
|
}
|
|
|
|
protected void afterSave() {
|
|
super.afterSave();
|
|
|
|
if (m_wasNew) {
|
|
// If the parent of this folder is null, and this folder
|
|
// is the root folder of its content section, then the
|
|
// content section is set as the context of this folder,
|
|
// so that permissions cascade correctly.
|
|
|
|
// NB: Because folder is saved before the creation of the
|
|
// content in the CMS Initializer, content section is null
|
|
// and this isn't run!
|
|
final ACSObject parent = getParent();
|
|
|
|
if (parent == null) {
|
|
// Verify that the content section is not null and
|
|
// this is its root folder.
|
|
|
|
final ContentSection section = getContentSection();
|
|
|
|
if (section != null && (this.equals(section.getRootFolder()) || this.
|
|
equals(section.getTemplatesFolder()))) {
|
|
PermissionService.setContext(this, section);
|
|
}
|
|
}
|
|
}
|
|
|
|
// All folder versions should inherit their permissions from the
|
|
// working version
|
|
final ContentItem workingVersion = getWorkingVersion();
|
|
|
|
if (!this.equals(workingVersion)) {
|
|
PermissionService.setContext(this, workingVersion);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the child items of this folder. The returned collection provides methods to filter by various criteria,
|
|
* for example by name or by whether items are folders or not.
|
|
*
|
|
* @param bSort whether to sort the collection by isFolder and ID
|
|
* @return child items of this folder
|
|
*/
|
|
public ItemCollection getItems(boolean bSort) {
|
|
|
|
DataQueryDataCollectionAdapter adapter = new DataQueryDataCollectionAdapter(ITEMS_QUERY, ITEM);
|
|
adapter.setParameter(PARENT, getID());
|
|
Assert.isNotEqual(PENDING, getVersion());
|
|
adapter.setParameter(VERSION, getVersion());
|
|
|
|
return new ItemCollection(adapter, bSort);
|
|
}
|
|
|
|
/**
|
|
* Fetches the child items of this folder. The returned collection provides methods to filter by various criteria,
|
|
* for example by name or by whether items are folders or not. The items returned by this method are sorted by
|
|
* isFolder and ID
|
|
*
|
|
* @return child items of this folder, sorted by isFolder and ID
|
|
*/
|
|
public ItemCollection getItems() {
|
|
return getItems(true);
|
|
}
|
|
|
|
/**
|
|
* Returns collection of primary language instances for bundles in this folder.
|
|
*/
|
|
public ItemCollection getPrimaryInstances() {
|
|
final DataQuery query = SessionManager.getSession().retrieveQuery(PRIMARY_INSTANCES_QUERY);
|
|
query.setParameter(PARENT, getID());
|
|
|
|
Assert.isNotEqual(PENDING, getVersion());
|
|
|
|
query.setParameter(VERSION, getVersion());
|
|
|
|
return new ItemCollection(query);
|
|
}
|
|
|
|
/**
|
|
* Returns a child content item in this folder (which could itself be a folder) with the specified name.
|
|
*
|
|
* @param name The name of the item
|
|
* @param isFolder If true, only return a subfolder. Otherwise, return any subitem
|
|
* @return The item with the given name, or null if no such item exists in the folder
|
|
*/
|
|
public ContentItem getItem(final String name,
|
|
final boolean isFolder) {
|
|
|
|
DataQuery query;
|
|
if (isFolder) {
|
|
query = SessionManager.getSession().retrieveQuery(FOLDER_QUERY);
|
|
} else {
|
|
query = SessionManager.getSession().retrieveQuery(ITEM_QUERY);
|
|
}
|
|
|
|
query.setParameter(PARENT, getID());
|
|
query.setParameter(VERSION, getVersion());
|
|
query.setParameter(NAME, name);
|
|
|
|
DataCollection items = new DataQueryDataCollectionAdapter(query, ITEM);
|
|
|
|
if (items.next()) {
|
|
DataObject dataObj = items.getDataObject();
|
|
ContentItem result = (ContentItem) DomainObjectFactory
|
|
.newInstance(dataObj);
|
|
|
|
if (items.next()) {
|
|
s_log.warn("Item in folder has a duplicate name; one " + "is " + result + " and one is "
|
|
+ (ContentItem) DomainObjectFactory
|
|
.newInstance(items.getDataObject()));
|
|
throw new IllegalStateException();
|
|
}
|
|
|
|
return result;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void addItem(final ContentBundle item) {
|
|
// TODO: saving the item here is a little weird, but
|
|
// the only way we can guarantee that it ever gets saved.
|
|
item.setParent(this);
|
|
|
|
item.save();
|
|
}
|
|
|
|
public String getDisplayName() {
|
|
final String result = getLabel();
|
|
|
|
if (result == null) {
|
|
return super.getDisplayName();
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the label of the folder.
|
|
*/
|
|
public final String getLabel() {
|
|
return (String) get(LABEL);
|
|
}
|
|
|
|
/**
|
|
* Set the label of this folder.
|
|
*/
|
|
public final void setLabel(final String value) {
|
|
if (s_log.isDebugEnabled()) {
|
|
s_log.debug("Setting label to " + value);
|
|
}
|
|
|
|
set(LABEL, value);
|
|
}
|
|
|
|
/**
|
|
* Set the version of the folder. An attempt to set the version to pending will result in the folder's version being
|
|
* set to live. We will never have any pending versions of folders, only live or draft.
|
|
*
|
|
* Pending versions of items are stored in the live version of a folder.
|
|
*/
|
|
protected void setVersion(String version) {
|
|
if (ContentItem.PENDING.equals(version)) {
|
|
version = ContentItem.LIVE;
|
|
}
|
|
|
|
super.setVersion(version);
|
|
}
|
|
|
|
//
|
|
// Publish/unpublish stuff
|
|
//
|
|
public void unpublish() {
|
|
if (s_log.isInfoEnabled()) {
|
|
s_log.info("Unpublishing folder " + this);
|
|
}
|
|
|
|
super.unpublish();
|
|
}
|
|
|
|
public ContentItem publish(final LifecycleDefinition cycleDef,
|
|
final Date startDate) {
|
|
if (s_log.isInfoEnabled()) {
|
|
s_log.info("Publishing folder " + this);
|
|
}
|
|
|
|
return super.publish(cycleDef, startDate);
|
|
}
|
|
|
|
//
|
|
// Lifecycle stuff
|
|
//
|
|
/**
|
|
* Always returns
|
|
* <code>null</code>, as folders do not have lifecycles.
|
|
*
|
|
* @return a <code>Lifecycle</code> value
|
|
*/
|
|
public Lifecycle getLifecycle() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Does not do anything, as folders do not have lifecycles.
|
|
*/
|
|
public void setLifecycle(final Lifecycle cycle) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Does not do anything, as folders do not have lifecycles.
|
|
*/
|
|
public void removeLifecycle() {
|
|
return;
|
|
}
|
|
|
|
protected void addPendingVersion(final ContentItem version) {
|
|
if (Assert.isEnabled()) {
|
|
assertDraft();
|
|
}
|
|
version.setVersion(LIVE);
|
|
//version.save();
|
|
setLiveVersion(version);
|
|
}
|
|
|
|
public void removePendingVersion(final ContentItem version) {
|
|
Assert.isNotEqual(PENDING, version.getVersion());
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Index item
|
|
//
|
|
/**
|
|
* Get the (special) index item for the folder. The index item is what carries all the user-editable attributes of
|
|
* the folder. The index item is what should be published when a index page for a folder is desired.
|
|
*
|
|
* The index item is an ordinary item in every respect, i.e., it is part of the collection returned by
|
|
* <code>getItems()</code>, you cannot delete a folder if it still has an index item etc.
|
|
*/
|
|
public ContentBundle getIndexItem() {
|
|
// BECAUSE INDEX ITEM MIGHT NOT BE UPDATED FOR PUBLISHED
|
|
// FOLDERS, CHECK IF DRAFT VERSIONS OF LIVE FOLDERS HAVE INDEX
|
|
// ITEM.
|
|
|
|
if (getVersion().compareTo(ContentItem.LIVE) == 0) {
|
|
final ContentItem indexItem =
|
|
((Folder) getWorkingVersion()).getIndexItem();
|
|
|
|
if (indexItem == null) {
|
|
return null;
|
|
}
|
|
|
|
return (ContentBundle) indexItem.getLiveVersion();
|
|
}
|
|
|
|
final DataObject index = (DataObject) get(INDEX);
|
|
|
|
if (index == null) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return (ContentBundle) DomainObjectFactory.newInstance(index);
|
|
} catch (PersistenceException pe) {
|
|
throw new UncheckedWrapperException(pe);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the index item. This also adds the item to the folder.
|
|
*
|
|
* @param item The index item with the folder's user-editable attributes
|
|
*/
|
|
public final void setIndexItem(final ContentBundle item) {
|
|
setAssociation(INDEX, item);
|
|
|
|
addItem(item); // XXX Why is this needed?
|
|
}
|
|
|
|
/**
|
|
* Removes the index item.
|
|
*/
|
|
public final void removeIndexItem() {
|
|
setAssociation(INDEX, null);
|
|
}
|
|
|
|
/**
|
|
* Returns
|
|
* <code>true</code> if the folder is empty.
|
|
*
|
|
* @return <code>true</code> if the folder is empty
|
|
*/
|
|
public boolean isEmpty() {
|
|
final Session session = SessionManager.getSession();
|
|
|
|
final DataQuery query = session.retrieveQuery("com.arsdigita.cms.folderNotEmpty");
|
|
query.setParameter("id", getID());
|
|
|
|
final boolean result = !query.next();
|
|
|
|
query.close();
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns
|
|
* <code>true</code> if the folder contains at least one folder,
|
|
* <code>false</code> if the folder does not contain any folders, but is either empty or contains only ordinary
|
|
* items.
|
|
*
|
|
* @return <code>true</code> if the folder contains other folders.
|
|
*/
|
|
public boolean containsFolders() {
|
|
final Session session = SessionManager.getSession();
|
|
|
|
final DataQuery query = session.retrieveQuery("com.arsdigita.cms.folderHasNoSubFolders");
|
|
query.setParameter("id", getID());
|
|
|
|
final boolean result = !query.next();
|
|
|
|
query.close();
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Copy the specified property (attribute or association) from the specified source folder. This method almost
|
|
* completely overrides the metadata-driven methods in
|
|
* <code>ObjectCopier</code>. If the property in question is an association to
|
|
* <code>ContentItem</code>(s), this method should <em>only</em> call
|
|
* <code>FooContentItem newChild =
|
|
* copier.copyItem(originalChild)</code>. An attempt to call any other method in order to copy the child will most
|
|
* likely have disastrous consequences.
|
|
*
|
|
* If a child class overrides this method, it should return
|
|
* <code>super.copyProperty</code> in order to indicate that it is not interested in handling the property in any
|
|
* special way.
|
|
*
|
|
* @param srcItem the source item
|
|
* @param property the property to copy
|
|
* @param copier the ItemCopier
|
|
* @return true if the property was copied, false to indicate that regular metadata-driven methods should be used to
|
|
* copy the property
|
|
*/
|
|
public boolean copyProperty(final CustomCopy srcItem,
|
|
final Property property,
|
|
final ItemCopier copier) {
|
|
// Ignore the items association.
|
|
String attrName = property.getName();
|
|
if (ITEMS.equals(attrName) || INDEX.equals(attrName)) {
|
|
return true;
|
|
}
|
|
|
|
return super.copyProperty(srcItem, property, copier);
|
|
}
|
|
|
|
/**
|
|
* Folders aren't explicitly p2fs'd
|
|
*/
|
|
protected boolean canPublishToFS() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A collection of items that can be filtered to return only folders or only nonfolders.
|
|
*/
|
|
public static class ItemCollection
|
|
extends com.arsdigita.cms.ItemCollection {
|
|
|
|
private final static String IS_FOLDER = "isFolder";
|
|
private final static String HAS_CHILDREN = "hasChildren";
|
|
private final static String ITEM = "item";
|
|
private final static String HAS_LIVE_VERSION = "hasLiveVersion";
|
|
private final static String TYPE_LABEL = "type.label";
|
|
private final static String AUDIT_TRAIL = "item.auditing";
|
|
private DataQuery m_query;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param adapter an adapter constructed using the query name rather than a DataQuery object. This constructor
|
|
* must be used if there is any intention to permission filter the results as only a
|
|
* DataQueryDataCollectionAdapter constructed using query name has the bug fix to allow permission filtering
|
|
*
|
|
* @param bSort whether to sort the collection by isFolder and ID
|
|
*/
|
|
public ItemCollection(DataQueryDataCollectionAdapter adapter, boolean bSort) {
|
|
super(adapter);
|
|
doAlias(adapter);
|
|
init(adapter, bSort);
|
|
}
|
|
|
|
public ItemCollection(DataQueryDataCollectionAdapter adapter) {
|
|
this(adapter, true);
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param query the Data Query to use to retrieve the collection
|
|
* @param bSort whether to sort the collection by isFolder and ID
|
|
*/
|
|
//ideally, we wouldn't sort the collection by default and only provide
|
|
//one constructor. But, that would break the existing API
|
|
public ItemCollection(DataQuery query, boolean bSort) {
|
|
super(new DataQueryDataCollectionAdapter(doAlias(query), ITEM));
|
|
|
|
init(query, bSort);
|
|
}
|
|
|
|
/**
|
|
* Convenience Constructor that always sorts the collection by isFolder and ID
|
|
*
|
|
* jensp 2011-06: I changed this because this silly sorting affects the ItemSearchWidget and makes it pretty
|
|
* useless... I've not noticed any negative effects, so it seams no problem. Sorting is now set by the
|
|
* caller/user of the {@code ItemCollection}.
|
|
*
|
|
* @param query the Data Query to use to retrieve the collection
|
|
*/
|
|
public ItemCollection(DataQuery query) {
|
|
super(new DataQueryDataCollectionAdapter(doAlias(query), ITEM));
|
|
|
|
init(query, false);
|
|
}
|
|
|
|
private void init(DataQuery query, boolean bSort) {
|
|
m_query = query;
|
|
|
|
if (bSort) {
|
|
m_query.addOrder("isFolder desc");
|
|
|
|
addOrder("id desc");
|
|
}
|
|
}
|
|
|
|
private static DataQuery doAlias(final DataQuery query) {
|
|
query.alias("isFolder", "isFolder");
|
|
|
|
return query;
|
|
}
|
|
|
|
/**
|
|
* Sets the range of the dataquery. This is used by the paginator.
|
|
*
|
|
* @param beginIndex The start index
|
|
* @param endIndex The end index
|
|
*/
|
|
public void setRange(final Integer beginIndex,
|
|
final Integer endIndex) {
|
|
m_dataQuery.setRange(beginIndex, endIndex);
|
|
}
|
|
|
|
public String getDisplayName() {
|
|
return (String) get(DISPLAY_NAME);
|
|
}
|
|
|
|
/**
|
|
* For performance reaons, override superclass methods and try to get the audit info without instantiating a
|
|
* content item. We know this can help because the getPrimaryInstances query retrieves the audit info directly
|
|
*/
|
|
public Date getCreationDate() {
|
|
DataObject dobj = (DataObject) get(AUDIT_TRAIL);
|
|
if (dobj != null) {
|
|
BasicAuditTrail audit = new BasicAuditTrail(dobj);
|
|
return audit.getCreationDate();
|
|
} else {
|
|
return super.getCreationDate();
|
|
}
|
|
}
|
|
|
|
public Date getLastModifiedDate() {
|
|
DataObject dobj = (DataObject) get(AUDIT_TRAIL);
|
|
if (dobj != null) {
|
|
BasicAuditTrail audit = new BasicAuditTrail(dobj);
|
|
return audit.getLastModifiedDate();
|
|
} else {
|
|
return super.getLastModifiedDate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the pretty name of the content type of the current item. If the current item is a folder, the string
|
|
* <tt>Folder</tt> is returned, otherwise the label of the item's content type.
|
|
*
|
|
* @return the pretty name of the content type of the current item.
|
|
*/
|
|
public String getTypeLabel() {
|
|
if (isFolder()) {
|
|
return "Folder";
|
|
} else {
|
|
return (String) get(TYPE_LABEL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filter the collection by whether items are folders or not.
|
|
*
|
|
* @param v <code>true</code> if the data query should only list folders, <code>false</code> if the data query
|
|
* should only list non-folder items.
|
|
*
|
|
*/
|
|
public void addFolderFilter(final boolean v) {
|
|
m_query.addEqualsFilter(IS_FOLDER, v ? "1" : "0");
|
|
}
|
|
|
|
/**
|
|
* Return
|
|
* <code>true</code> if the current item in the collection is a folder.
|
|
*
|
|
* @return <code>true</code> if the current item in the collection is a folder.
|
|
*/
|
|
public boolean isFolder() {
|
|
Boolean result = (Boolean) m_query.get(IS_FOLDER);
|
|
return result.booleanValue();
|
|
}
|
|
|
|
public boolean hasChildren() {
|
|
Boolean result = (Boolean) m_query.get(HAS_CHILDREN);
|
|
return result.booleanValue();
|
|
}
|
|
|
|
public boolean isLive() {
|
|
String version = (String) get(ContentItem.VERSION);
|
|
if (ContentItem.LIVE.equals(version)) {
|
|
return true;
|
|
}
|
|
Boolean hasLive = (Boolean) m_query.get(HAS_LIVE_VERSION);
|
|
return hasLive.booleanValue();
|
|
}
|
|
|
|
/**
|
|
* Only used on CollectionS returned by getPrimaryInstance()
|
|
*/
|
|
public BigDecimal getBundleID() {
|
|
if (isFolder()) {
|
|
return null;
|
|
} else {
|
|
return (BigDecimal) m_query.get("bundleID");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by
|
|
* <code>VersionCopier</code> to determine whether to publish associated items when an item goes live. This will
|
|
* only have an effect for non-component associations where the item is not yet published. Override default for
|
|
* <code>Folder</code>s since they don't have their own lifecycles and a folder must be published when an item in it
|
|
* goes live.
|
|
*
|
|
* @return whether to publish this item
|
|
*/
|
|
public boolean autoPublishIfAssociated() {
|
|
return true;
|
|
}
|
|
|
|
public static void setUserHomeFolder(User user, Folder folder) {
|
|
UserHomeFolderMap map = UserHomeFolderMap.findOrCreateUserHomeFolderMap(user, folder.getContentSection());
|
|
map.setHomeFolder(folder);
|
|
map.save();
|
|
}
|
|
|
|
public static Folder getUserHomeFolder(User user, ContentSection section) {
|
|
Folder folder = null;
|
|
UserHomeFolderMap map = UserHomeFolderMap.findUserHomeFolderMap(user, section);
|
|
if (map != null) {
|
|
folder = map.getHomeFolder();
|
|
if (folder != null) {
|
|
CMSContext context = CMS.getContext();
|
|
SecurityManager sm;
|
|
if (context.hasSecurityManager()) {
|
|
sm = CMS.getContext().getSecurityManager();
|
|
} else {
|
|
sm = new SecurityManager(section);
|
|
}
|
|
if (!sm.canAccess(user, SecurityConstants.PREVIEW_PAGES, folder)) {
|
|
folder = null;
|
|
}
|
|
}
|
|
}
|
|
return folder;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a folder by its path from a given content section.
|
|
*
|
|
* @param section The content section from which the folder should be retrieved.
|
|
* @param path The path of the folder, relative to the content section.
|
|
* @return The folder with the given path from the provided content section. If there is no such folder,
|
|
* {@code null} is returned. It is up to the caller to check the returned value for {@code null} and take
|
|
* appropriate actions.
|
|
*/
|
|
public static Folder retrieveFolder(final ContentSection section, final String path) {
|
|
if (section == null) {
|
|
throw new IllegalArgumentException("No content section provided.");
|
|
}
|
|
|
|
if ((path == null) || path.isEmpty()) {
|
|
throw new IllegalArgumentException("No path provided.");
|
|
}
|
|
|
|
if (path.charAt(0) != '/') {
|
|
throw new IllegalArgumentException("Provided path is not an absolute path (starting with '/').");
|
|
}
|
|
|
|
final String[] pathTokens = path.split("/");
|
|
|
|
final Folder rootFolder = section.getRootFolder();
|
|
|
|
Folder folder = rootFolder;
|
|
for (String token : pathTokens) {
|
|
if ((token == null) || token.isEmpty() || "/".equals(token)) {
|
|
continue;
|
|
}
|
|
|
|
folder = getSubFolder(token, folder);
|
|
|
|
if (folder == null) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return folder;
|
|
}
|
|
|
|
private static Folder getSubFolder(final String name, final Folder fromFolder) {
|
|
final ItemCollection items = fromFolder.getItems();
|
|
items.addFolderFilter(true);
|
|
items.addNameFilter(name);
|
|
|
|
if (items.next()) {
|
|
return (Folder) items.getDomainObject();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a folder of the current content section by its path. The path is given in a UNIX like synatax and must
|
|
* be an absolute path starting with '/'. The path may be precceded with the name of content section, separated by
|
|
* ':'. If a content section if given, the path is relative to this content section. If the no content section is
|
|
* given, the current content section returned by {@code CMS.getContext().getContentSection()}. Please note that
|
|
* {@code CMS.getContext().getContentSection()} may return null.
|
|
*
|
|
* Examples for valid paths:
|
|
*
|
|
* <pre>
|
|
* /persons/members
|
|
* content:/persons/members
|
|
* publications:/monographs
|
|
* </pre>
|
|
*
|
|
* @param path The path of the folder to retrieve relative to the current content section.
|
|
* @return The folder with the given path from the content section. If there is no such folder, {@code null} is
|
|
* returned. It is up to the caller to check the returned value for {@code null} and take appropriate actions.
|
|
*/
|
|
public static Folder retrieveFolder(final String path) {
|
|
final String[] tokens = path.split(":");
|
|
|
|
if (tokens.length == 1) {
|
|
return retrieveFolder(CMS.getContext().getContentSection(), path);
|
|
} else if (tokens.length == 2) {
|
|
final ContentSectionCollection sections = ContentSection.getAllSections();
|
|
sections.addEqualsFilter("label", tokens[0]);
|
|
|
|
if (sections.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
sections.next();
|
|
final ContentSection section = sections.getContentSection();
|
|
return retrieveFolder(section, tokens[1]);
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Invalid path syntax. Valid syntax: "
|
|
+ "[contentsection:]/path/to/folder'");
|
|
}
|
|
}
|
|
}
|