diff --git a/ccm-portlet-bookmarks/application.xml b/ccm-portlet-bookmarks/application.xml
new file mode 100644
index 000000000..f976dfe7a
--- /dev/null
+++ b/ccm-portlet-bookmarks/application.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Portlet that allows users to store internal and external links.
+
+ Ported from West Sussex contribution: ccm-wsx-bookmarks-portlet, created by
+ Chris Gilbert.
+
+
diff --git a/ccm-portlet-bookmarks/pdl/com/arsdigita/portlet/BookmarksPortlet.pdl b/ccm-portlet-bookmarks/pdl/com/arsdigita/portlet/BookmarksPortlet.pdl
new file mode 100644
index 000000000..157de5c6e
--- /dev/null
+++ b/ccm-portlet-bookmarks/pdl/com/arsdigita/portlet/BookmarksPortlet.pdl
@@ -0,0 +1,81 @@
+// author chris gilbert
+// model for a portlet that holds a list of bookmark links
+
+model uk.gov.westsussex.portlet;
+
+import com.arsdigita.portal.Portlet;
+import com.arsdigita.cms.contenttypes.Link;
+
+object type BookmarksPortlet extends Portlet {
+
+}
+
+
+object type Bookmark extends Link {
+
+}
+
+
+association {
+
+
+ component Bookmark[0..n] bookmarks = join portlets.portlet_id to portlet_bookmarks.portlet_id,
+ join portlet_bookmarks.target_id to cms_links.link_id;
+ BookmarksPortlet[0..1] portlet = join cms_links.link_id to portlet_bookmarks.target_id,
+ join portlet_bookmarks.portlet_id to portlets.portlet_id;
+
+
+
+}
+
+data operation swapRelatedLinkWithNextInGroup {
+ do {
+ update cms_links
+ set link_order = CASE WHEN (link_order = :linkOrder) THEN
+ (:nextLinkOrder)
+ ELSE
+ (:linkOrder)
+ END
+ where (link_order = :linkOrder or link_order = :nextLinkOrder)
+ and (select portlet_id from portlet_bookmarks where target_id=link_id) = :ownerID
+ and 2 = (select count(*) from cms_links l, portlet_bookmarks b
+ where l.link_id=b.target_id
+ and (link_order = :linkOrder or link_order = :nextLinkOrder)
+ and portlet_id = :ownerID)
+ }
+}
+
+query minRelatedLinkOrderForPortlet {
+ Integer linkOrder;
+
+ options {
+ WRAP_QUERIES = false;
+ }
+
+ do {
+ select min(link_order) as link_order from cms_links l, portlet_bookmarks b
+ where b.portlet_id = :ownerID and l.link_id = b.target_id
+ } map {
+ linkOrder = link_order;
+ }
+}
+
+query maxRelatedLinkOrderForPortlet {
+ Integer linkOrder;
+
+ options {
+ WRAP_QUERIES = false;
+ }
+
+ do {
+ select max(link_order) as link_order from cms_links l, portlet_bookmarks b
+ where b.portlet_id = :ownerID and l.link_id = b.target_id
+ } map {
+ linkOrder = link_order;
+ }
+}
+
+
+
+
+
diff --git a/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/oracle-se-create.sql b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/oracle-se-create.sql
new file mode 100644
index 000000000..1c59f651a
--- /dev/null
+++ b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/oracle-se-create.sql
@@ -0,0 +1,2 @@
+@ ddl/oracle-se/create.sql
+@ ddl/oracle-se/deferred.sql
diff --git a/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/postgres-create.sql b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/postgres-create.sql
new file mode 100644
index 000000000..2a326455c
--- /dev/null
+++ b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/postgres-create.sql
@@ -0,0 +1,4 @@
+begin;
+\i ddl/postgres/create.sql
+\i ddl/postgres/deferred.sql
+end;
diff --git a/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/upgrade/oracle-se-1.0.1-1.0.2.sql b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/upgrade/oracle-se-1.0.1-1.0.2.sql
new file mode 100644
index 000000000..307361992
--- /dev/null
+++ b/ccm-portlet-bookmarks/sql/ccm-portlet-bookmarks/upgrade/oracle-se-1.0.1-1.0.2.sql
@@ -0,0 +1,14 @@
+update cms_links x
+set link_order = nvl((select sort_key
+ from portlet_bookmarks y
+ where y.target_id = x.link_id), x.link_order);
+commit;
+
+alter table portlet_bookmarks
+drop column sort_key;
+
+update acs_objects
+set object_type = 'uk.gov.westsussex.portlet.Bookmark',
+ default_domain_class = 'uk.gov.westsussex.portlet.bookmarks.Bookmark'
+where object_id in (select target_id
+ from portlet_bookmarks);
\ No newline at end of file
diff --git a/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.config b/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.config
new file mode 100644
index 000000000..ee8a150bc
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.config
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.load b/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.load
new file mode 100644
index 000000000..6e53425e9
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/ccm-portlet-bookmarks.load
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Bookmark.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Bookmark.java
new file mode 100644
index 000000000..7d18bd0bb
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Bookmark.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+import com.arsdigita.cms.contenttypes.Link;
+import com.arsdigita.domain.DataObjectNotFoundException;
+import com.arsdigita.persistence.DataCollection;
+import com.arsdigita.persistence.DataObject;
+import com.arsdigita.persistence.DataOperation;
+import com.arsdigita.persistence.DataQuery;
+import com.arsdigita.persistence.OID;
+import com.arsdigita.persistence.Session;
+import com.arsdigita.persistence.SessionManager;
+
+/**
+ *
+ * Extends link, but has no additional data. Extension is to enable overriding
+ * of swap methods which don't work in Link - if you delete a link in the middle
+ * you cannot move links past the resulting gap
+ *
+ * @author Chris Gilbert (cgyg9330)
+ */
+public class Bookmark extends Link implements BookmarkConstants{
+
+ /** Private Logger instance for debugging purpose. */
+ private static Logger s_log = Logger.getLogger(Bookmark.class);
+
+ /** PDL stuff */
+ public static String BASE_DATA_OBJECT_TYPE =
+ "com.arsdigita.portlet.Bookmark";
+
+ public Bookmark() {
+ super(BASE_DATA_OBJECT_TYPE);
+ }
+
+ public Bookmark(DataObject data) {
+ super(data);
+ }
+
+ public Bookmark( OID id ) throws DataObjectNotFoundException {
+ super( id );
+ }
+
+ public Bookmark( BigDecimal id ) throws DataObjectNotFoundException {
+ this( new OID( BASE_DATA_OBJECT_TYPE, id ) );
+ }
+
+ /**
+ * Swaps this Bookmark with the next one,
+ * according to the linkOrder
+ */
+ @Override
+ public void swapWithNext() {
+ s_log.debug("Start - swapWithNext");
+ swapWithNext("com.arsdigita.portlet.minRelatedLinkOrderForPortlet",
+ "com.arsdigita.portlet.swapRelatedLinkWithNextInGroup");
+ }
+
+ /**
+ * Swaps this Bookmark with the previous one,
+ * according to the linkOrder
+ */
+ @Override
+ public void swapWithPrevious() {
+ s_log.debug("Start - swapWithPrevious");
+ swapWithPrevious("com.arsdigita.portlet.maxRelatedLinkOrderForPortlet",
+ "com.arsdigita.portlet.swapRelatedLinkWithNextInGroup");
+ }
+
+ /**
+ * Given a dataquery name, returns the (possibly filtered)
+ * DataQuery for use in swapKeys. This implementation filters
+ * on the portlet property, so that only
+ * Bookmarks which belong to the same BookmarksPortlet
+ * will be swapped.
+ *
+ * @param queryName name of the DataQuery to use
+ * @return the DataQuery
+ */
+ @Override
+ protected DataQuery getSwapQuery(String queryName) {
+ DataQuery query = super.getSwapQuery(queryName);
+ BigDecimal portletID = BookmarksPortlet
+ .getPortletForBookmark(this).getID();
+ s_log.debug("setting owner in swapquerie as " + portletID);
+ query.setParameter("ownerID", portletID);
+ return query;
+ }
+
+ /**
+ * Given a data operation name, returns the
+ * DataOperation for use in swapKeys. This implementation sets the
+ * portlet parameter, in addition to what is set by
+ * super.getSwapOperation
+ *
+ * @param operationName the Name of the DataOperation to use
+ *
+ * @return the DataOperation used to swap the sort keys.
+ */
+ @Override
+ protected DataOperation getSwapOperation(String operationName) {
+ DataOperation operation = super.getSwapOperation(operationName);
+ BigDecimal portletID =
+ BookmarksPortlet.getPortletForBookmark(this).getID();
+ s_log.debug("setting owner in swapoperation as " + portletID);
+
+ operation.setParameter("ownerID", portletID);
+ return operation;
+ }
+
+ /**
+ * This method is only used for setting initial sort keys for
+ * links which exist without them. This is called by swapKeys
+ * instead of attempting to swap if the key found is
+ * null. This implementation sorts all Bookmarks owned by this
+ * Bookmark's portlet by title.
+ */
+ @Override
+ protected void alphabetize() {
+ Session session = SessionManager.getSession();
+ DataCollection links = session.retrieve(BASE_DATA_OBJECT_TYPE);
+ links.addEqualsFilter( PORTLET + ".id",
+ BookmarksPortlet.getPortletForBookmark(this).getID());
+ links.addOrder(TITLE);
+ int sortKey = 0;
+ while (links.next()) {
+ sortKey++;
+ Link link = new Bookmark(links.getDataObject());
+ link.setOrder(sortKey);
+ link.save();
+ }
+
+ }
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarkConstants.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarkConstants.java
new file mode 100644
index 000000000..328ee84b8
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarkConstants.java
@@ -0,0 +1,63 @@
+/*
+ * Created on 10-Sep-04
+ *
+ * To change the template for this generated file go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package com.arsdigita.portlet.bookmarks;
+
+import com.arsdigita.bebop.Label;
+import com.arsdigita.globalization.GlobalizedMessage;
+
+/**
+ * @author cgyg9330
+ *
+ * Constants used by classes in the bookmarksportlet application.
+ *
+ */
+
+public interface BookmarkConstants {
+
+ public static final String BUNDLE_NAME = "uk.gov.westsussex.portlet.bookmarks.ui.BookmarkResources";
+
+
+ // bookmark portlet attributes
+ public static final String BOOKMARKS = "bookmarks";
+ public static final String PORTLET = "portlet";
+ public static final String SORT_KEY = "sortKey";
+
+ // bookmark attribute values (nb target types defined in com.arsdigita.cms.contenttypes.Link used
+
+ public static final String NEW_WINDOW_YES = "_blank";
+ public static final String NEW_WINDOW_NO = null;
+
+
+ // rendering
+
+ public static final String XML_BOOKMARK_NS = "http://wsgfl.westsussex.gov.uk/portlet/bookmarks/1.0";
+ public static final String MAIN_BOOKMARK_PORTLET_ELEMENT = "portlet:bookmarks";
+ public static final String BOOKMARK_ELEMENT = "bookmark-portlet:bookmark";
+ public static final String TITLE_ATTRIBUTE = "title";
+ public static final String URL_ATTRIBUTE = "url";
+ public static final String WINDOW_ATTRIBUTE = "target-window";
+
+
+ // editing
+
+ public static final String NO_BOOKMARKS_YET = (String)new GlobalizedMessage("bookmarks.header.no-bookmarks", BUNDLE_NAME).localize();
+ public static final String EXISTING_BOOKMARKS = (String)new GlobalizedMessage("bookmarks.header.existing-bookmarks", BUNDLE_NAME).localize();
+ public static final String NEW_WINDOW = (String)new GlobalizedMessage("bookmarks.new-window", BUNDLE_NAME).localize();
+ public static final Label ADD_NEW_BOOKMARK_LABEL = new Label((String)new GlobalizedMessage("bookmarks.add", BUNDLE_NAME).localize(), Label.BOLD);
+ public static final Label TITLE_LABEL = new Label((String)new GlobalizedMessage("bookmarks.title", BUNDLE_NAME).localize(), Label.BOLD);
+ public static final Label DESCRIPTION_LABEL = new Label((String)new GlobalizedMessage("bookmarks.description", BUNDLE_NAME).localize(), Label.BOLD);
+ public static final Label URL_LABEL = new Label((String)new GlobalizedMessage("bookmarks.url", BUNDLE_NAME).localize(), Label.BOLD);
+
+ // errors
+
+ public static final String NO_URL = (String)new GlobalizedMessage("bookmarks.error.no-url", BUNDLE_NAME).localize();
+ public static final String NO_TITLE = (String)new GlobalizedMessage("bookmarks.error.no-title", BUNDLE_NAME).localize();
+ public static final String CONTENT_ITEM_NOT_FOUND = (String)new GlobalizedMessage("bookmarks.error.content-item-not-found", BUNDLE_NAME).localize();
+ public static final String CONTENT_ITEM_NOT_AVAILABLE = (String)new GlobalizedMessage("bookmarks.warning.item-not-available", BUNDLE_NAME).localize();
+
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortlet.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortlet.java
new file mode 100644
index 000000000..162632d02
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortlet.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks;
+
+
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.portal.AbstractPortletRenderer;
+import com.arsdigita.cms.ContentItem;
+import com.arsdigita.cms.SecurityManager;
+import com.arsdigita.cms.contenttypes.Link;
+import com.arsdigita.cms.dispatcher.ItemResolver;
+import com.arsdigita.cms.dispatcher.MultilingualItemResolver;
+import com.arsdigita.kernel.Kernel;
+import com.arsdigita.kernel.Party;
+import com.arsdigita.kernel.permissions.PermissionService;
+import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
+import com.arsdigita.persistence.DataAssociation;
+import com.arsdigita.persistence.DataAssociationCursor;
+import com.arsdigita.persistence.DataCollection;
+import com.arsdigita.persistence.DataObject;
+import com.arsdigita.persistence.Filter;
+import com.arsdigita.persistence.FilterFactory;
+import com.arsdigita.persistence.SessionManager;
+import com.arsdigita.portal.Portlet;
+import com.arsdigita.portlet.bookmarks.ui.BookmarksPortletRenderer;
+import com.arsdigita.web.URL;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Displays a collection of links, that may be internal or external.
+ *
+ * @author cgyg9330
+ */
+public class BookmarksPortlet extends Portlet implements BookmarkConstants {
+
+ /** Private Logger instance for debugging purpose. */
+ public static final Logger s_log = Logger.getLogger(BookmarksPortlet.class);
+
+ public static final String BASE_DATA_OBJECT_TYPE =
+ "uk.gov.westsussex.portlet.BookmarksPortlet";
+
+ private static BookmarksPortletConfig s_config =
+ new BookmarksPortletConfig();
+
+ static {
+ s_config.load();
+ }
+
+ public static BookmarksPortletConfig getConfig() {
+ return s_config;
+ }
+
+ public BookmarksPortlet(DataObject dataObject) {
+ super(dataObject);
+ }
+
+ @Override
+ protected String getBaseDataObjectType() {
+ return BASE_DATA_OBJECT_TYPE;
+ }
+
+ @Override
+ protected AbstractPortletRenderer doGetPortletRenderer() {
+ return new BookmarksPortletRenderer(this);
+
+ }
+ /**
+ * associate a Bookmark with this portlet and place it after existing
+ * Bookmarks
+ *
+ * @param bookmark
+ */
+ public void addBookmark(Bookmark bookmark) {
+ DataAssociationCursor bookmarks =
+ ((DataAssociation) get(BOOKMARKS)).cursor();
+ bookmarks.addOrder(Link.ORDER + " desc");
+ int key = 1;
+ if (bookmarks.next()) {
+ key = ((Integer) bookmarks.get(Link.ORDER)).intValue() + 1;
+
+ }
+ bookmarks.close();
+ bookmark.setOrder(key);
+ add(BOOKMARKS, bookmark);
+
+ }
+
+ /**
+ * Return a url for an internal or external link - used for displaying
+ * the link in edit view (we don't want to just display an item id
+ * when the link is internal as that doesn't mean anything to non authors)
+ *
+ * @param link
+ * @param state
+ * @return
+ */
+ public static String getURIForBookmark(Bookmark bookmark, PageState state) {
+
+ String url = null;
+
+ if (bookmark.getTargetType().equals(Link.EXTERNAL_LINK)) {
+ url = bookmark.getTargetURI();
+ } else {
+
+ ItemResolver resolver = new MultilingualItemResolver();
+ ContentItem item = bookmark.getTargetItem();
+ if (item != null) {
+ item = item.getLiveVersion();
+ if (item != null) {
+
+ url =
+ resolver.generateItemURL(
+ state,
+ item,
+ item.getContentSection(),
+ ContentItem.LIVE);
+ url = URL.there(state.getRequest(), url).toString();
+ }
+
+ }
+
+ }
+ return url;
+ }
+
+ /**
+ * Retrieve the bookmarks for this portlet in their correct order
+ * @return
+ */
+ public DataCollection getBookmarks() {
+
+ DataAssociationCursor cursor =
+ ((DataAssociation) get(BOOKMARKS)).cursor();
+ cursor.addOrder(Link.ORDER);
+ if (getConfig().checkPermissions()) {
+
+ Party party = Kernel.getContext().getParty();
+ if (party == null) {
+ party = Kernel.getPublicUser();
+ }
+
+ FilterFactory factory = cursor.getFilterFactory();
+ /* cursor.addFilter(PermissionService.getFilterQuery(factory, "targetItem",
+ PrivilegeDescriptor.get(SecurityManager.CMS_READ_ITEM),
+ party.getOID()));*/
+
+ // need to or it here so that we don't exclude external links
+ Filter filter =
+ factory.or().addFilter(
+ factory.equals("targetItem", null)).addFilter(
+ PermissionService.getFilterQuery(
+ factory,
+ "targetItem",
+ PrivilegeDescriptor.get(SecurityManager.CMS_READ_ITEM),
+ party.getOID()));
+ // this extra and 1=1 is a fiddle to ensure the query is bracketed properly.
+ // I needed it to be
+ // 'get links for this portlet and ((link is not a content item) or (user has access to item))'
+ // but just adding the or filter produced
+ // 'get links for this portlet and (link is not a content item) or (user has access to item)'
+ // with the result that all content item links were returned regardless of which portlet they belonged to
+ cursor.addFilter(factory.and().addFilter(filter).addFilter("1=1"));
+
+ }
+
+ return cursor;
+ }
+
+ /**
+ *
+ * Return the owner of the given bookmark
+ *
+ * @param bookmark
+ * @return
+ */
+ public static BookmarksPortlet getPortletForBookmark(Bookmark bookmark) {
+ // what the hell's this????? bookmark <-> portlet is a two way association
+ // so we should be using the association programatically to get the portlet
+ // Never mind, I'm sure it boils down to the same sql anyway
+ DataCollection portlets =
+ SessionManager.getSession().retrieve(
+ BookmarksPortlet.BASE_DATA_OBJECT_TYPE);
+ portlets.addEqualsFilter(BOOKMARKS, bookmark.getID());
+ BookmarksPortlet portlet = null;
+ while (portlets.next()) {
+ portlet = new BookmarksPortlet(portlets.getDataObject());
+ }
+ return portlet;
+
+ }
+
+ public void renumberBookmarks() {
+ DataCollection bookmarks = getBookmarks();
+ bookmarks.addOrder(Link.ORDER);
+ int sortKey = 0;
+ while (bookmarks.next()) {
+ sortKey++;
+ Bookmark link = new Bookmark(bookmarks.getDataObject());
+ link.setOrder(sortKey);
+
+ }
+
+ }
+}
+
+
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig.java
new file mode 100644
index 000000000..9e9f19b25
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003 Chris Gilbert 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.portlet.bookmarks;
+
+import com.arsdigita.runtime.AbstractConfig;
+import com.arsdigita.util.parameter.BooleanParameter;
+import com.arsdigita.util.parameter.Parameter;
+
+/**
+ * Configuration allows links to be access controlled (so homepage admin
+ * can add links that only selected users can see).
+ *
+ * @author Chris Gilbert (cgyg9330) <chris.gilbert@westsussex.gov.uk>
+ * @version $Id: BookmarksPortletConfig.java 2007/08/08 09:28:26 cgyg9330 $
+ */
+public class BookmarksPortletConfig extends AbstractConfig {
+
+ private BooleanParameter checkPermissions = new BooleanParameter(
+ "uk.gov.westsussex.portlet.bookmarks.checkPermissions",
+ Parameter.REQUIRED, new Boolean(false));
+
+ public BookmarksPortletConfig() {
+
+ register(checkPermissions);
+ loadInfo();
+ }
+
+ public boolean checkPermissions() {
+ return ((Boolean) get(checkPermissions)).booleanValue();
+ }
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig_parameter.properties b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig_parameter.properties
new file mode 100644
index 000000000..560aa6744
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/BookmarksPortletConfig_parameter.properties
@@ -0,0 +1,4 @@
+uk.gov.westsussex.portlet.bookmarks.checkPermissions.title=Check for access to bookmarks
+uk.gov.westsussex.portlet.bookmarks.checkPermissions.purpose=allow home page admin to create links which only certain users can see
+uk.gov.westsussex.portlet.bookmarks.checkPermissions.example=false
+uk.gov.westsussex.portlet.bookmarks.checkPermissions.format=[boolean]
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Initializer.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Initializer.java
new file mode 100644
index 000000000..4e64f405b
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Initializer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks;
+
+
+import com.arsdigita.bebop.RequestLocal;
+import com.arsdigita.db.DbHelper;
+import com.arsdigita.domain.DomainObject;
+import com.arsdigita.kernel.ACSObjectInstantiator;
+import com.arsdigita.kernel.ResourceType;
+import com.arsdigita.kernel.ResourceTypeConfig;
+import com.arsdigita.kernel.ui.ResourceConfigFormSection;
+import com.arsdigita.persistence.DataObject;
+import com.arsdigita.persistence.pdl.ManifestSource;
+import com.arsdigita.persistence.pdl.NameFilter;
+import com.arsdigita.portal.PortletType;
+import com.arsdigita.portlet.bookmarks.ui.BookmarksPortletAdder;
+import com.arsdigita.portlet.bookmarks.ui.BookmarksPortletEditor;
+import com.arsdigita.runtime.CompoundInitializer;
+import com.arsdigita.runtime.DomainInitEvent;
+import com.arsdigita.runtime.PDLInitializer;
+import com.arsdigita.runtime.RuntimeConfig;
+
+/**
+ * based on com.arsdigita.london.portal.installer.portlet
+ *
+ * @author cgyg9330 (Chris Gilbert)
+ * @version $Id: Initializer.java,v 1.3 2005/06/08 14:45:43 cgyg9330 Exp $
+ */
+public class Initializer extends CompoundInitializer {
+
+
+ /**
+ * Constructor.
+ */
+ public Initializer() {
+ final String url = RuntimeConfig.getConfig().getJDBCURL();
+ final int database = DbHelper.getDatabaseFromURL(url);
+
+ add(
+ new PDLInitializer(
+ new ManifestSource(
+ "ccm-wsx-bookmarks-portlet.pdl.mf",
+ new NameFilter(
+ DbHelper.getDatabaseSuffix(database),
+ "pdl"))));
+ }
+
+ /**
+ *
+ * @param e
+ */
+ @Override
+ public void init(DomainInitEvent e) {
+ super.init(e);
+
+ e.getFactory().registerInstantiator(
+ BookmarksPortlet.BASE_DATA_OBJECT_TYPE,
+ new ACSObjectInstantiator() {
+ @Override
+ public DomainObject doNewInstance(DataObject dataObject) {
+ return new BookmarksPortlet(dataObject);
+ }
+ });
+
+ e.getFactory().registerInstantiator(
+ Bookmark.BASE_DATA_OBJECT_TYPE,
+ new ACSObjectInstantiator() {
+ @Override
+ public DomainObject doNewInstance(DataObject dataObject) {
+ return new Bookmark(dataObject);
+ }
+ });
+
+ new ResourceTypeConfig(BookmarksPortlet.BASE_DATA_OBJECT_TYPE) {
+ @Override
+ public ResourceConfigFormSection getCreateFormSection(
+ final ResourceType resType,
+ final RequestLocal parentAppRL) {
+
+ final ResourceConfigFormSection config =
+ new BookmarksPortletAdder(resType, parentAppRL);
+
+ return config;
+ }
+
+ @Override
+ public ResourceConfigFormSection getModifyFormSection(
+ final RequestLocal application) {
+
+ final BookmarksPortletEditor config =
+ new BookmarksPortletEditor(application);
+ return config;
+ }
+ };
+
+
+ /**
+ * implementation of framework that allows portlets to be bundled up
+ * as discrete applications
+ */
+ PortletType.registerXSLFile(BookmarksPortlet.BASE_DATA_OBJECT_TYPE,
+ "/packages/westsussex-portlets/xsl/bookmarks-portlet.xsl");
+
+ }
+}
\ No newline at end of file
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader.java
new file mode 100644
index 000000000..02f3dbe5c
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks;
+
+import com.arsdigita.kernel.Kernel;
+import com.arsdigita.kernel.KernelExcursion;
+import com.arsdigita.loader.PackageLoader;
+import com.arsdigita.portal.PortletType;
+import com.arsdigita.runtime.ScriptContext;
+import com.arsdigita.util.parameter.Parameter;
+import com.arsdigita.util.parameter.StringParameter;
+
+
+/**
+ * Just create the portlet type - includes Load parameter (that can be
+ * set via interactive load) for the type name, though this can be
+ * changed in the DB later if required in application_types table
+ *
+ * @author cgyg9330
+ * @version $Id: Loader.java,v 1.1 2005/02/25 08:41:56 cgyg9330 Exp $
+ */
+public class Loader extends PackageLoader {
+
+ private StringParameter typeName = new StringParameter
+ ("uk.gov.westsussex.portlet.bookmarks.name",
+ Parameter.REQUIRED, "My Links");
+
+ public Loader() {
+ register(typeName);
+
+ }
+ public void run(final ScriptContext ctx) {
+ new KernelExcursion() {
+ public void excurse() {
+ setEffectiveParty(Kernel.getSystemParty());
+ PortletType type = PortletType
+ .createPortletType((String)get(typeName),
+ PortletType.WIDE_PROFILE,
+ BookmarksPortlet.BASE_DATA_OBJECT_TYPE);
+ type.setDescription("Allows users to maintain a list of internal and external links");
+ }
+ }.run();
+ }
+
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader_parameter.properties b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader_parameter.properties
new file mode 100644
index 000000000..d59cf088d
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/Loader_parameter.properties
@@ -0,0 +1,5 @@
+uk.gov.westsussex.portlet.bookmarks.name.title=Portlet Type Name
+uk.gov.westsussex.portlet.bookmarks.name.purpose=The name of the portlet type that appears in the drop down list of portlet types
+uk.gov.westsussex.portlet.bookmarks.name.example=Bookmarks
+uk.gov.westsussex.portlet.bookmarks.name.format=[string]
+
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkResources.properties b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkResources.properties
new file mode 100644
index 000000000..3f16d5a19
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkResources.properties
@@ -0,0 +1,12 @@
+bookmarks.header.no-bookmarks=No links defined yet
+bookmarks.header.existing-bookmarks=Existing links (check to delete)
+bookmarks.new-window=open in a new window
+bookmarks.add=Specify the full address (starting http://) - you can cut and paste addresses from this site:
+bookmarks.description=Description:
+bookmarks.title=Title:
+bookmarks.url=URL:
+bookmarks.error.no-url=Please enter a url
+bookmarks.error.no-title=Please enter a title for the link
+bookmarks.error.content-item-not-found=Page could not be found on this site
+bookmarks.warning.item-not-available=Page not currently available on this site
+
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkSelectionModel.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkSelectionModel.java
new file mode 100644
index 000000000..fe6d4554f
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarkSelectionModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks.ui;
+
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.parameters.BigDecimalParameter;
+import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
+import com.arsdigita.portlet.bookmarks.Bookmark;
+
+
+/**
+ * SelectionModel to track the current Bookmark object for view/edit
+ * purposes. - copied from authoring ui
+ * Bit pointless really - it just saves a bit of casting. Just as easy
+ * to use ACSObjectSelectionModel direct
+ */
+public class BookmarkSelectionModel extends ACSObjectSelectionModel {
+
+ public BookmarkSelectionModel(BigDecimalParameter param) {
+ super(Bookmark.class.getName(),
+ Bookmark.BASE_DATA_OBJECT_TYPE,
+ param);
+ }
+
+ /**
+ * Construct a new BookmarkSelectionModel
+ *
+ * @param itemClass The name of the Java class which represents
+ * the content item. Must be a subclass of Link. In
+ * addition, the class must have a constructor with a single
+ * OID parameter.
+ * @param objectType The name of the persistence metadata object type
+ * which represents the content item. In practice, will often be
+ * the same as the itemClass.
+ * @param parameter The state parameter which should be used by this item
+ */
+ public BookmarkSelectionModel(String itemClass, String objectType,
+ BigDecimalParameter parameter) {
+ super(itemClass, objectType, parameter);
+ }
+
+ /**
+ * Returns the currently-selected Bookmark
+ *
+ * @param state the PageState for the current request.
+ * @return The current Bookmark
+ */
+ public Bookmark getSelectedLink(PageState state) {
+ return (Bookmark)getSelectedObject(state);
+ }
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletAdder.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletAdder.java
new file mode 100644
index 000000000..d6ab72d27
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletAdder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks.ui;
+
+import com.arsdigita.portlet.bookmarks.BookmarkConstants;
+
+import com.arsdigita.bebop.ColumnPanel;
+import com.arsdigita.bebop.HorizontalLine;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.RequestLocal;
+import com.arsdigita.bebop.portal.PortletConfigFormSection;
+import com.arsdigita.kernel.ResourceType;
+
+/**
+ * Adder creates a new bookmarks portlet without any bookmarks.
+ *
+ * Note this is the only portlet I have seen where a different form is
+ * registered for adding and editing.
+ *
+ * It is necessary here because we need to create a persistent portlet instance
+ * before we can associate links with it.
+ *
+ * @author cgyg9330
+ */
+public class BookmarksPortletAdder
+ extends PortletConfigFormSection
+ implements BookmarkConstants {
+
+
+ public BookmarksPortletAdder(
+ ResourceType resType,
+ RequestLocal parentAppRL) {
+ super(resType, parentAppRL);
+ }
+
+
+ protected void addWidgets() {
+
+ /*add(
+ new Label(
+ "Create links to your favourite websites or pages on this site. Specify the full address (starting http://) - you can cut and paste addresses from this site.",
+ Label.BOLD),
+ ColumnPanel.FULL_WIDTH);*/
+ super.addWidgets();
+ add(new HorizontalLine(), ColumnPanel.FULL_WIDTH);
+
+ add(
+ new Label(
+ "Give the portlet a title and save it now, then edit it to add bookmarks.",
+ Label.BOLD),
+ ColumnPanel.FULL_WIDTH);
+ add(new HorizontalLine(), ColumnPanel.FULL_WIDTH);
+
+
+ }
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletEditor.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletEditor.java
new file mode 100644
index 000000000..f75f2c438
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletEditor.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks.ui;
+
+import java.net.MalformedURLException;
+import java.util.StringTokenizer;
+import java.util.TooManyListenersException;
+
+
+import com.arsdigita.bebop.ColumnPanel;
+import com.arsdigita.bebop.FormProcessException;
+import com.arsdigita.bebop.HorizontalLine;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.Page;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.RequestLocal;
+import com.arsdigita.bebop.event.PrintEvent;
+import com.arsdigita.bebop.event.PrintListener;
+import com.arsdigita.bebop.form.CheckboxGroup;
+import com.arsdigita.bebop.form.Option;
+import com.arsdigita.bebop.form.TextField;
+import com.arsdigita.bebop.parameters.BigDecimalParameter;
+import com.arsdigita.bebop.parameters.NotNullValidationListener;
+import com.arsdigita.bebop.parameters.StringParameter;
+import com.arsdigita.bebop.portal.PortletConfigFormSection;
+import com.arsdigita.bebop.portal.PortletSelectionModel;
+import com.arsdigita.cms.ContentItem;
+import com.arsdigita.cms.ContentSection;
+import com.arsdigita.cms.ContentSectionCollection;
+import com.arsdigita.cms.SecurityManager;
+import com.arsdigita.cms.contenttypes.Link;
+import com.arsdigita.cms.dispatcher.CMSDispatcher;
+import com.arsdigita.cms.dispatcher.ItemResolver;
+import com.arsdigita.portal.Portlet;
+import com.arsdigita.portlet.bookmarks.Bookmark;
+import com.arsdigita.portlet.bookmarks.BookmarkConstants;
+import com.arsdigita.portlet.bookmarks.BookmarksPortlet;
+import com.arsdigita.util.UncheckedWrapperException;
+import com.arsdigita.web.Application;
+import com.arsdigita.web.URL;
+import com.arsdigita.web.Web;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Allows definition of new bookmarks to add to the portlet, and edit, move,
+ * deletion of existing bookmarks.
+ *
+ * @author cgyg9330
+ */
+public class BookmarksPortletEditor
+ extends PortletConfigFormSection
+ implements BookmarkConstants {
+
+ private static final Logger s_log =
+ Logger.getLogger(BookmarksPortletEditor.class);
+
+ private TextField m_title;
+ private TextField m_description;
+ private TextField m_url;
+ private CheckboxGroup m_newWindow;
+
+ private BookmarksTable m_existingBookmarks;
+ private BigDecimalParameter m_selectedBookmark;
+ private BookmarkSelectionModel m_bookmarkSelectionModel;
+ private BigDecimalParameter m_selectedPortlet;
+ private PortletSelectionModel m_portletSelectionModel;
+
+
+ /**
+ *
+ * @param application
+ */
+ public BookmarksPortletEditor(RequestLocal application) {
+ super(application);
+ }
+
+
+ /**
+ * store item in requestlocal for access by various methods
+ *
+ * code copied from content item portlet - > may be problems when items
+ * unpublished - check
+ */
+ private RequestLocal contentItem = new RequestLocal() {
+ @Override
+ protected Object initialValue(PageState ps) {
+ String userURL = (String) m_url.getValue(ps);
+ java.net.URL contextURL;
+ try {
+ contextURL =
+ new java.net.URL(
+ Web.getRequest().getRequestURL().toString());
+ } catch (MalformedURLException ex) {
+ throw new UncheckedWrapperException(ex);
+ }
+
+ java.net.URL url;
+ try {
+ url = new java.net.URL(contextURL, userURL);
+ } catch (MalformedURLException ex) {
+ s_log.info("Malformed URL " + userURL);
+ return null;
+ }
+
+ String dp = URL.getDispatcherPath();
+ String path = url.getPath();
+ if (path.startsWith(dp)) {
+ path = path.substring(dp.length());
+ }
+
+ StringTokenizer tok = new StringTokenizer(path, "/");
+ if (!tok.hasMoreTokens()) {
+ s_log.info(
+ "Couldn't find a content section for "
+ + path
+ + " in "
+ + userURL);
+ return null;
+ }
+
+ String sectionPath = '/' + tok.nextToken() + '/';
+
+ String context = ContentItem.LIVE;
+ if (tok.hasMoreTokens()
+ && CMSDispatcher.PREVIEW.equals(tok.nextToken())) {
+
+ context = CMSDispatcher.PREVIEW;
+ }
+
+ ContentSectionCollection sections = ContentSection.getAllSections();
+ sections.addEqualsFilter(Application.PRIMARY_URL, sectionPath);
+
+ ContentSection section;
+ if (sections.next()) {
+ section = sections.getContentSection();
+ sections.close();
+ } else {
+ s_log.info(
+ "Content section "
+ + sectionPath
+ + " in "
+ + userURL
+ + " doesn't exist.");
+ return null;
+ }
+
+ ItemResolver resolver = section.getItemResolver();
+
+ path = path.substring(sectionPath.length());
+
+ if (path.endsWith(".jsp")) {
+ path = path.substring(0, path.length() - 4);
+ }
+
+ ContentItem item = resolver.getItem(section, path, context);
+ if (item == null) {
+ s_log.debug("Couldn't resolve item " + path);
+ return null;
+ }
+
+ SecurityManager sm = new SecurityManager(item.getContentSection());
+
+ boolean canRead =
+ sm.canAccess(
+ ps.getRequest(),
+ SecurityManager.PUBLIC_PAGES,
+ item);
+ if (!canRead) {
+ s_log.debug("User not allowed access to item");
+ return null;
+
+ }
+
+ return item.getDraftVersion();
+ }
+ };
+
+
+ /**
+ * register the parameter that records the current selected bookmark
+ */
+ @Override
+ public void register (Page p) {
+ super.register(p);
+ p.addComponentStateParam(this, m_selectedBookmark);
+ p.addComponentStateParam(this, m_selectedPortlet);
+ }
+
+ /**
+ *
+ */
+ @Override
+ protected void addWidgets() {
+ // create widgets
+ m_url = new TextField(new StringParameter(Link.TARGET_URI));
+ m_url.addValidationListener(new NotNullValidationListener());
+
+ m_title = new TextField(new StringParameter(Link.DISPLAY_NAME));
+ m_title.addValidationListener(new NotNullValidationListener());
+
+ m_description = new TextField(new StringParameter(Link.DESCRIPTION));
+
+ m_newWindow = new CheckboxGroup(Link.TARGET_WINDOW);
+ m_newWindow.addOption(new Option(NEW_WINDOW_YES, NEW_WINDOW));
+
+ try {
+ m_newWindow.addPrintListener(new PrintListener() {
+
+ public void prepare(PrintEvent e) {
+ PageState state = e.getPageState();
+ CheckboxGroup newWindow = (CheckboxGroup)e.getTarget();
+ if (m_bookmarkSelectionModel.isSelected(state)) {
+ Link link = m_bookmarkSelectionModel.getSelectedLink(state);
+ newWindow.setValue(state, link.getTargetWindow());
+
+ }
+
+ }
+ });
+ } catch (IllegalArgumentException e) {
+ s_log.warn("exception when trying to set checkbox value", e);
+ } catch (TooManyListenersException e) {
+ s_log.warn("exception when trying to set checkbox value", e);
+ }
+
+
+ m_selectedPortlet = new BigDecimalParameter("bookmark_portlet");
+ m_portletSelectionModel = new PortletSelectionModel(m_selectedPortlet);
+ m_selectedBookmark = new BigDecimalParameter("bookmark");
+ m_bookmarkSelectionModel = new BookmarkSelectionModel(
+ "uk.gov.westsussex.portlet.bookmarks.Bookmark",
+ Bookmark.BASE_DATA_OBJECT_TYPE, m_selectedBookmark);
+ m_existingBookmarks = new BookmarksTable(m_bookmarkSelectionModel,
+ m_portletSelectionModel);
+
+ super.addWidgets();
+
+ add(new HorizontalLine(), ColumnPanel.FULL_WIDTH);
+
+
+ add(m_existingBookmarks, ColumnPanel.FULL_WIDTH);
+ add(new HorizontalLine(), ColumnPanel.FULL_WIDTH);
+
+ add(ADD_NEW_BOOKMARK_LABEL, ColumnPanel.FULL_WIDTH);
+ add(TITLE_LABEL, ColumnPanel.RIGHT);
+ add(m_title);
+ add(DESCRIPTION_LABEL, ColumnPanel.RIGHT);
+ add(m_description);
+
+
+ add(URL_LABEL, ColumnPanel.RIGHT);
+ add(m_url);
+ add(new Label("")); // fill up the left hand column
+ add(m_newWindow);
+
+ }
+
+ /**
+ * specify current portlet for reference by table
+ *
+ * fill in values if user has selected edit on an existing bookmark
+ */
+ @Override
+ protected void initWidgets(PageState state, Portlet portlet)
+ throws FormProcessException {
+ s_log.debug("init widgets - set selected portlet to " + portlet.getOID());
+ // set cached version as dirty here rather than during process
+ // in case an action link is pressed (eg to move links up/down)
+ portlet.getPortletRenderer().invalidateCachedVersion(state);
+ m_portletSelectionModel.setSelectedObject(state, portlet);
+ super.initWidgets(state, portlet);
+ if (m_bookmarkSelectionModel.isSelected(state)) {
+ Bookmark link = m_bookmarkSelectionModel.getSelectedLink(state);
+ m_url.setValue(state, BookmarksPortlet.getURIForBookmark(link, state));
+ m_description.setValue(state, link.getDescription());
+ m_title.setValue(state, link.getTitle());
+
+ }
+ }
+
+ /**
+ * Validates url if it looks like it is trying to be a content item
+ * but is failing
+ */
+ @Override
+ public void validateWidgets(PageState state, Portlet portlet)
+ throws FormProcessException {
+ // m_selectedPortlet.set(state, portlet);
+ super.validateWidgets(state, portlet);
+
+ String fullUrl = (String) m_url.getValue(state);
+ s_log.debug("fullURL = " + fullUrl);
+
+ Object item = contentItem.get(state);
+ URL here = URL.here(state.getRequest(), null);
+ String thisSite = here.getServerURI();
+ s_log.debug("This site is " + thisSite);
+ if (item == null && fullUrl.indexOf(thisSite) != -1
+ && fullUrl.indexOf("/ccm/") != -1
+ && fullUrl.indexOf("/content/") != -1 ) {
+ // not watertight, but is reasonable check that user is trying
+ // to specify a content item on this site
+ throw new FormProcessException(CONTENT_ITEM_NOT_FOUND);
+ }
+
+ }
+ /**
+ * add new bookmark to portlet, or amend existing one if we are editing
+ * a selected bookmark
+ */
+ @Override
+ protected void processWidgets(PageState state, Portlet portlet)
+ throws FormProcessException {
+ s_log.debug("START processWidgets");
+ super.processWidgets(state, portlet);
+
+
+ BookmarksPortlet myportlet = (BookmarksPortlet) portlet;
+
+ String titleText = (String) m_title.getValue(state);
+ String urlText = (String) m_url.getValue(state);
+ String descriptionText = (String)m_description.getValue(state);
+
+ ContentItem item = (ContentItem) contentItem.get(state);
+ String[] newWindowValue = (String[]) m_newWindow.getValue(state);
+ String newWindow =
+ newWindowValue == null ? NEW_WINDOW_NO : NEW_WINDOW_YES;
+
+ Bookmark newBookmark;
+
+ if (m_bookmarkSelectionModel.isSelected(state)) {
+ newBookmark = m_bookmarkSelectionModel.getSelectedLink(state);
+
+ } else {
+ newBookmark = new Bookmark();
+ myportlet.addBookmark(newBookmark);
+
+ }
+ newBookmark.setTitle(titleText);
+ newBookmark.setDescription(descriptionText);
+ newBookmark.setTargetWindow(newWindow);
+ if (item == null) {
+ newBookmark.setTargetType(Link.EXTERNAL_LINK);
+ newBookmark.setTargetURI(urlText);
+
+ } else {
+ newBookmark.setTargetType(Link.INTERNAL_LINK);
+ newBookmark.setTargetItem(item);
+ }
+
+
+ m_bookmarkSelectionModel.clearSelection(state);
+ s_log.debug("END processWidgets");
+ }
+
+
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletRenderer.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletRenderer.java
new file mode 100644
index 000000000..e1a484c9f
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksPortletRenderer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks.ui;
+
+import com.arsdigita.portlet.bookmarks.Bookmark;
+import com.arsdigita.portlet.bookmarks.BookmarkConstants;
+import com.arsdigita.portlet.bookmarks.BookmarksPortlet;
+
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.portal.AbstractPortletRenderer;
+// import com.arsdigita.portalworkspace.ui.PortalConstants;
+import com.arsdigita.persistence.DataCollection;
+import com.arsdigita.portal.PortletType;
+import com.arsdigita.xml.Element;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author cgyg9330
+ *
+ *
+ */
+public class BookmarksPortletRenderer extends AbstractPortletRenderer
+ implements BookmarkConstants {
+
+ private static Logger s_log = Logger.getLogger(BookmarksPortletRenderer.class);
+
+ private BookmarksPortlet portlet;
+
+
+ /**
+ * Constructor.
+ *
+ * @param portlet
+ */
+ public BookmarksPortletRenderer(BookmarksPortlet portlet) {
+ this.portlet = portlet;
+ }
+
+
+ /* (non-Javadoc)
+ * @see com.arsdigita.bebop.portal.
+ * AbstractPortletRenderer#generateBodyXML(
+ * com.arsdigita.bebop.PageState,
+ * com.arsdigita.xml.Element)
+ */
+ protected void generateBodyXML(PageState state, Element document) {
+ s_log.debug("START generateBodyXML");
+
+ Element main = document.newChildElement(MAIN_BOOKMARK_PORTLET_ELEMENT,
+ PortletType.PORTLET_XML_NS);
+ // PortalConstants.PORTLET_XML_NS);
+ DataCollection links = portlet.getBookmarks();
+
+ while (links.next()) {
+ Bookmark link = new Bookmark (links.getDataObject());
+ String url = BookmarksPortlet.getURIForBookmark(link, state);
+ if (url != null) {
+ Element linkElement = createLink(link, state);
+ main.addContent(linkElement);
+ }
+ }
+
+ s_log.debug("END generateBodyXML");
+ }
+
+
+ private Element createLink(Bookmark bookmark, PageState state) {
+ String url = BookmarksPortlet.getURIForBookmark(bookmark, state);
+ Element link = new Element(BOOKMARK_ELEMENT, XML_BOOKMARK_NS);
+ link.addAttribute(TITLE_ATTRIBUTE, bookmark.getTitle());
+ link.addAttribute(
+ URL_ATTRIBUTE,
+ url);
+ link.addAttribute(WINDOW_ATTRIBUTE, bookmark.getTargetWindow());
+
+ return link;
+ }
+
+
+ /**
+ * cache key is portlet id
+ */
+ @Override
+ public String getCacheKey(PageState state) {
+ return portlet.getID().toString();
+ }
+
+ /**
+ * if we are checking permissions, don't use cache, otherwise refresh
+ * when portlet has been edited
+ */
+ @Override
+ public boolean isDirty(PageState state) {
+ if (!BookmarksPortlet.getConfig().checkPermissions()){
+// if cached version exists then it isn't dirty
+ // because cached version is dropped whenever the portlet is edited
+ // (see init method in editor class)
+ return false;
+ } else {
+ return true;
+
+ }
+
+ }
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTable.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTable.java
new file mode 100644
index 000000000..32ce9ccb9
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTable.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2005 Chris Gilbert 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.portlet.bookmarks.ui;
+
+
+import com.arsdigita.bebop.Component;
+import com.arsdigita.bebop.ControlLink;
+import com.arsdigita.bebop.ExternalLink;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.RequestLocal;
+import com.arsdigita.bebop.Table;
+import com.arsdigita.bebop.event.TableActionEvent;
+import com.arsdigita.bebop.event.TableActionListener;
+import com.arsdigita.bebop.portal.PortletSelectionModel;
+import com.arsdigita.bebop.table.TableCellRenderer;
+import com.arsdigita.bebop.table.TableColumn;
+import com.arsdigita.bebop.table.TableColumnModel;
+import com.arsdigita.cms.contenttypes.ui.LinkTableModelBuilder;
+import com.arsdigita.domain.DataObjectNotFoundException;
+import com.arsdigita.domain.DomainObjectFactory;
+import com.arsdigita.persistence.OID;
+import com.arsdigita.persistence.PersistenceException;
+import com.arsdigita.portlet.bookmarks.Bookmark;
+import com.arsdigita.portlet.bookmarks.BookmarksPortlet;
+import com.arsdigita.util.Assert;
+import com.arsdigita.util.UncheckedWrapperException;
+
+import java.math.BigDecimal;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Bebop table copied from authoring ui
+ *
+ * @version $Revision: 1.2 $ $Date: 2007/08/08 09:28:26 $
+ * @author Chris Gilbert
+ */
+public class BookmarksTable extends Table {
+
+ private static final Logger s_log = Logger.getLogger(BookmarksTable.class);
+
+ private BookmarkSelectionModel m_linkModel;
+ // private ItemSelectionModel m_itemModel;
+ private TableColumn m_titleCol;
+ private TableColumn m_descCol;
+ private TableColumn m_moveUpCol;
+ private TableColumn m_moveDownCol;
+ private TableColumn m_editCol;
+ private TableColumn m_delCol;
+
+ private RequestLocal m_size;
+ private PortletSelectionModel m_portlet;
+
+
+ protected static final String EDIT_EVENT = "Edit";
+ protected static final String DELETE_EVENT = "Delete";
+ protected static final String UP_EVENT = "up";
+ protected static final String DOWN_EVENT = "down";
+
+ /**
+ * Constructor. Creates a BookmarksTable given a
+ * PortletSelectionModel and a
+ * BookmarkSelectionModel, which track the current portlet
+ * and link.
+ *
+ * @param item The ItemSelectionModel for the current page.
+ * @param link The LinkSelectionModel to track the
+ * current link
+ */
+ public BookmarksTable(BookmarkSelectionModel link, PortletSelectionModel portlet) {
+ super();
+ m_portlet = portlet;
+ setModelBuilder(new BookmarksTableModelBuilder(portlet));
+
+ m_linkModel = link;
+ addColumns();
+
+ m_size = new RequestLocal();
+
+ Label empty = new Label("There are no links defined yet");
+ setEmptyView(empty);
+ addTableActionListener(new LinkTableActionListener());
+ setRowSelectionModel(m_linkModel);
+ setDefaultCellRenderer(new LinkTableRenderer());
+ }
+
+ /**
+ * Sets up the columns to display.
+ */
+ protected void addColumns() {
+ TableColumnModel model = getColumnModel();
+ int i = 0;
+ m_titleCol = new TableColumn(i, "Link");
+ m_descCol = new TableColumn(++i, "Description");
+ m_editCol = new TableColumn(++i, "Edit");
+ m_delCol = new TableColumn(++i, "Delete");
+ m_moveUpCol = new TableColumn(++i, "");
+ m_moveDownCol = new TableColumn(++i, "");
+ model.add(m_titleCol);
+ model.add(m_descCol);
+ model.add(m_editCol);
+ model.add(m_delCol);
+ model.add(m_moveUpCol);
+ model.add(m_moveDownCol);
+ setColumnModel(model);
+ }
+
+ /**
+ * TableCellRenderer class for LinkTable
+ */
+ private class LinkTableRenderer implements TableCellRenderer {
+ public Component getComponent(Table table,
+ PageState state,
+ Object value,
+ boolean isSelected,
+ Object key,
+ int row,
+ int column) {
+ Bookmark link = (Bookmark)value;
+ boolean isFirst = (row == 0);
+ if (m_size.get(state) == null) {
+ m_size.set(state,
+ new Long(((LinkTableModelBuilder.LinkTableModel)
+ table.getTableModel(state)).size()));
+ }
+ boolean isLast = (row == ((Long)m_size.get(state)).intValue() - 1);
+
+ String url = BookmarksPortlet.getURIForBookmark(link, state);//link.getInternalOrExternalURI(state);
+ ExternalLink extLink = null;
+ if (column == m_titleCol.getModelIndex()) {
+ if (null != url) {
+ extLink = new ExternalLink(link.getTitle(), url);
+ extLink.setTargetFrame(link.getTargetWindow());
+ } else {
+ extLink = new ExternalLink("Broken Link", url);
+
+ }
+ return extLink;
+ } else if ( column == m_descCol.getModelIndex()) {
+ if ( isSelected ) {
+ return new Label(url == null? "Page has been unpublished" : link.getDescription(), Label.BOLD);
+ } else {
+ return new Label(url == null? "Page has been unpublished" : link.getDescription());
+ }
+ } else if ( column == m_editCol.getModelIndex()) {
+ if (isSelected ) {
+ return new Label(EDIT_EVENT, Label.BOLD);
+ } else {
+ return new ControlLink(EDIT_EVENT);
+ }
+
+ } else if ( column == m_delCol.getModelIndex()) {
+ return new ControlLink(DELETE_EVENT);
+
+ } else if (column == m_moveUpCol.getModelIndex()) {
+ if (!isFirst) {
+ Label upLabel = new Label(UP_EVENT);
+ upLabel.setClassAttr("linkSort");
+ return new ControlLink(upLabel);
+ } else {
+ return new Label("");
+ }
+ } else if (column == m_moveDownCol.getModelIndex()) {
+ if (!isLast) {
+ Label downLabel = new Label(DOWN_EVENT);
+ downLabel.setClassAttr("linkSort");
+ return new ControlLink(downLabel);
+ } else {
+ return new Label("");
+ }
+ } else {
+ throw new UncheckedWrapperException("column out of bounds");
+ }
+ }
+ }
+
+ /**
+ * TableActionListener class for LinkTable
+ */
+ private class LinkTableActionListener implements TableActionListener {
+ private Bookmark getLink(TableActionEvent e) {
+ Object o = e.getRowKey();
+ BigDecimal id;
+ if (o instanceof String) {
+ s_log.debug("row key is a string : " + o);
+ id = new BigDecimal((String)o);
+ } else {
+ id = (BigDecimal)e.getRowKey();
+ }
+
+ Assert.exists(id);
+ Bookmark link;
+ try {
+ link = (Bookmark)DomainObjectFactory.newInstance(new OID(Bookmark.BASE_DATA_OBJECT_TYPE,id));
+ } catch (DataObjectNotFoundException de) {
+ throw new UncheckedWrapperException(de);
+ }
+ return link;
+ }
+
+ public void cellSelected(TableActionEvent e) {
+ int col = e.getColumn().intValue();
+ PageState state = e.getPageState();
+ Bookmark link = getLink(e);
+ Assert.exists(link);
+
+ if (col== m_titleCol.getModelIndex()) {
+ // do nothing
+ } else if (col == m_editCol.getModelIndex()) {
+ // This selection is passed to the LinkPropertyForm
+ s_log.debug("setting linkModel to :" + link.getTitle());
+ m_linkModel.setSelectedObject(state, link);
+
+ } else if (col == m_delCol.getModelIndex()) {
+ try {
+ s_log.debug("About to delete");
+ m_linkModel.clearSelection(state);
+ link.delete();
+ BookmarksPortlet portlet = (BookmarksPortlet)m_portlet.getSelectedPortlet(state);
+ portlet.renumberBookmarks();
+ } catch ( PersistenceException pe) {
+ throw new UncheckedWrapperException(pe);
+ }
+
+ } else if (col == m_moveUpCol.getModelIndex() ) {
+ // move the link up
+ m_linkModel.clearSelection(state);
+ link.swapWithPrevious();
+ } else if ( col == m_moveDownCol.getModelIndex() ) {
+ // move the link down
+ m_linkModel.clearSelection(state);
+ link.swapWithNext();
+ }
+ }
+
+ public void headSelected(TableActionEvent e) {}
+ }
+}
diff --git a/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTableModelBuilder.java b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTableModelBuilder.java
new file mode 100644
index 000000000..0d6fecc61
--- /dev/null
+++ b/ccm-portlet-bookmarks/src/com/arsdigita/portlet/bookmarks/ui/BookmarksTableModelBuilder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 Red Hat Inc. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Open Software License v2.1
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://rhea.redhat.com/licenses/osl2.1.html.
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ */
+package com.arsdigita.portlet.bookmarks.ui;
+
+import org.apache.log4j.Logger;
+
+import com.arsdigita.portlet.bookmarks.BookmarksPortlet;
+
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.portal.PortletSelectionModel;
+import com.arsdigita.cms.contenttypes.ui.LinkTableModelBuilder;
+import com.arsdigita.persistence.DataCollection;
+
+/**
+ * Copied from authoting UI
+ *
+ * @version $Revision: 1.3 $ $Date: 2007/08/08 09:28:26 $
+ * @author Chris Gilbert
+ */
+
+public class BookmarksTableModelBuilder
+ extends LinkTableModelBuilder {
+ private static final Logger s_log =
+ Logger.getLogger(BookmarksTableModelBuilder.class);
+
+ private PortletSelectionModel m_portlet;
+
+ /**
+ * Constructor. Creates a BookmarksTableModelBuilder given a
+ * PortletSelectionModel
+ *
+ * @param portlet The PortletSelectionModel that refers to the current portlet.
+ *
+ */
+ public BookmarksTableModelBuilder(PortletSelectionModel portlet) {
+ m_portlet = portlet;
+ s_log.debug("BookmarksTableModelBuilder");
+ }
+
+ /**
+ * Returns the DataCollection of Bookmarks for the current
+ * TableModel.
+ *
+ * @param s The PageState for the current request
+ * @return The DataCollection of Bookmarks
+ */
+ public DataCollection getLinks(PageState state) {
+ BookmarksPortlet portlet = (BookmarksPortlet)m_portlet.getSelectedPortlet(state);
+ s_log.debug("Getting related links for " + portlet.getOID());
+ return portlet.getBookmarks();
+
+ }
+}
diff --git a/ccm-portlet-bookmarks/web/themes/heirloom/images/portlets/arrow_bullet.gif b/ccm-portlet-bookmarks/web/themes/heirloom/images/portlets/arrow_bullet.gif
new file mode 100644
index 000000000..1e385a5c0
Binary files /dev/null and b/ccm-portlet-bookmarks/web/themes/heirloom/images/portlets/arrow_bullet.gif differ
diff --git a/ccm-portlet-bookmarks/web/themes/heirloom/portlets/bookmarks-portlet.xsl b/ccm-portlet-bookmarks/web/themes/heirloom/portlets/bookmarks-portlet.xsl
new file mode 100644
index 000000000..bdf8ee33b
--- /dev/null
+++ b/ccm-portlet-bookmarks/web/themes/heirloom/portlets/bookmarks-portlet.xsl
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+ No links defined yet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
\ No newline at end of file