Index: webapp/cms/skins/demo/services/calendar/pages/services/agenda/agenda_1.3.xsl
===================================================================
--- webapp/cms/skins/demo/services/calendar/pages/services/agenda/agenda_1.3.xsl	(revision 25541)
+++ webapp/cms/skins/demo/services/calendar/pages/services/agenda/agenda_1.3.xsl	(working copy)
@@ -29,5 +29,27 @@
         <xsl:comment>[if IE 8]&gt;&lt;link rel="stylesheet" href="<xsl:value-of select="ametys:skinURL('css/services/agenda.ie.css')" />" type="text/css" /&gt;&lt;![endif]</xsl:comment>
          <xsl:comment>[if IE 7]&gt;&lt;link rel="stylesheet" href="<xsl:value-of select="ametys:skinURL('css/services/agenda.ie7.css')" />" type="text/css" /&gt;&lt;![endif]</xsl:comment>    
     </xsl:template> 
+    
+    <xsl:template name="rss">
+        <xsl:if test="$rssUrl != ''">
+            <div class="rss">
+                <a>
+                	<xsl:attribute name="href">
+                		<xsl:value-of select="$site-uri-prefix"/>
+                		<xsl:text>/</xsl:text>
+                		<xsl:value-of select="$rssUrl"/>
+                	</xsl:attribute>
+                	<xsl:choose>
+                		<xsl:when test="ametys:skinResources('img/rssicon.png')">
+		                	<img src="{ametys:skinURL('img/rssicon.png')}" i18n:attr="alt title" title="plugin.web:PLUGINS_WEB_SERVICE_FILTERED_CONTENTS_RSS_FEED_ALT" alt="plugin.web:PLUGINS_WEB_SERVICE_FILTERED_CONTENTS_RSS_FEED_ALT"/>
+                		</xsl:when>
+                		<xsl:otherwise>
+                			<i18n:text i18n:key="PLUGINS_WEB_SERVICE_FILTERED_CONTENTS_RSS_FEED_ALT" i18n:catalogue="plugin.web"/>
+                		</xsl:otherwise>
+                	</xsl:choose>
+                </a>
+            </div>
+        </xsl:if>    
+    </xsl:template>  
 		
 </xsl:stylesheet>		
Index: webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/actions/SetFilterInRequestAttributesAction.java
===================================================================
--- webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/actions/SetFilterInRequestAttributesAction.java	(revision 0)
+++ webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/actions/SetFilterInRequestAttributesAction.java	(revision 0)
@@ -0,0 +1,145 @@
+package org.ametys.plugins.calendar.actions;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.ametys.cms.filter.ContentFilterExtensionPoint;
+import org.ametys.plugins.calendar.events.EventsFilter;
+import org.ametys.plugins.calendar.events.EventsFilterHelper;
+import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.repository.AmetysRepositoryException;
+import org.ametys.plugins.repository.metadata.CompositeMetadata;
+import org.ametys.plugins.repository.query.expression.Expression;
+import org.ametys.web.filter.WebContentFilter;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.page.ZoneItem;
+import org.ametys.web.repository.site.SiteManager;
+import org.ametys.web.tags.TagCategory;
+import org.apache.avalon.framework.parameters.ParameterException;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.joda.time.DateTime;
+
+public class SetFilterInRequestAttributesAction extends org.ametys.web.repository.page.actions.SetFilterInRequestAttributesAction
+{
+	
+	/** */
+	private String _view;
+	/** */
+	private String _rangeType;
+	/** */
+	private Set<String> _tags;
+	/** */
+	private Set<TagCategory> _categories;
+	/** */
+	private int _year;
+	/** */
+	private int _month;
+	/** */
+	private int _day;
+	/** */
+	private int _monthsBefore;
+	/** */
+	private int _monthsAfter;
+	 /** The filter extension point. */
+    protected ContentFilterExtensionPoint _filterExtPt;
+	
+	/** The events helper. */
+    protected EventsFilterHelper _eventsFilterHelper;
+
+	@Override
+    public void service(ServiceManager smanager) throws ServiceException
+    {
+        super.service(smanager);
+        _eventsFilterHelper = (EventsFilterHelper) smanager.lookup(EventsFilterHelper.ROLE);
+        _filterExtPt = (ContentFilterExtensionPoint) smanager.lookup(ContentFilterExtensionPoint.ROLE);
+    }
+	
+	/**
+     * Get the content filter from a ZoneItem.
+     * @param parameters the action parameters, including the ZoneItem id.
+     * @param result the action result.
+     * @return the content filter.
+     * @throws ParameterException
+     */
+	@Override
+    protected WebContentFilter _getFilterFromZoneItem(Parameters parameters, Map<String, String> result) throws ParameterException
+    {
+        WebContentFilter filter = null;
+        
+        String zoneItemId = parameters.getParameter("zoneItemId");
+        _view = parameters.getParameter("view");
+        
+        
+        DateTime today = new DateTime();
+        _year = parameters.getParameterAsInteger("year", today.getYear());
+        _month = parameters.getParameterAsInteger("month", today.getMonthOfYear());
+        _day = parameters.getParameterAsInteger("day", today.getDayOfMonth());
+        
+
+        ZoneItem zoneItem = _resolver.resolveById("zoneItem://" + zoneItemId);
+        if (_isValid(zoneItem))
+        {
+        	_monthsBefore  = (int) zoneItem.getServiceParameters().getLong("months-before", 3);
+        	_monthsAfter  = (int) zoneItem.getServiceParameters().getLong("months-after", 3);
+        	
+            // Creates filter from services parameters
+        	_rangeType = parameters.getParameter("rangeType", _eventsFilterHelper.getDefaultRangeType(zoneItem));
+        	
+        	_tags = _eventsFilterHelper.getTags(zoneItem, parameters.getParameter("tags", null));
+        	
+        	String siteName = zoneItem.getZone().getPage().getSiteName();
+        	
+        	// Get the categories of the tags to match, from the zone item or from
+            // the parameters.
+            _categories = _eventsFilterHelper.getTagCategories(zoneItem, siteName, parameters.getParameter("tag-categories", null));
+        	
+        	
+            filter = _getFilterFromZoneItem(zoneItem);
+        }
+
+        Page page = zoneItem.getZone().getPage();
+        result.put("siteName", page.getSiteName());
+        result.put("lang", page.getSitemapName());
+        result.put("path", page.getPathInSitemap());
+
+        return filter;
+    }
+    
+	@Override
+    protected WebContentFilter _getFilterFromZoneItem(ZoneItem zoneItem) throws ParameterException
+    {
+		EventsFilter filter = (EventsFilter) _filterExtPt.getExtension(EventsFilterHelper.EVENTS_FILTER_ID);
+		
+		String type = "agenda";
+		
+		EventsFilterHelper.DateRange dateRange = _eventsFilterHelper.getDateRange(type, _year, _month, _day, _monthsBefore, _monthsAfter, _rangeType);
+        Expression expression = _eventsFilterHelper.getExpression(type, dateRange);
+        
+        Set<String> contentTypes = _eventsFilterHelper.getContentTypes(zoneItem);
+
+        boolean maskOrphan = _eventsFilterHelper.getMaskOrphan(zoneItem);
+        
+        // Get all tags belonging to the categories.
+        Set<String> categoryTags = _eventsFilterHelper.getAllTags(_categories);        
+        
+        // Configure the filter
+        _eventsFilterHelper.configureFilter(filter, expression, contentTypes, _tags, categoryTags, _view, maskOrphan);
+
+        return filter;
+    }
+	
+	/**
+     * Test if the ZoneItem is valid.
+     * @param zoneItem the ZoneItem.
+     * @return true if the ZoneItem is valid, false otherwise.
+     */
+	@Override
+    protected boolean _isValid(ZoneItem zoneItem)
+    {
+        return zoneItem.getType().equals(ZoneItem.ZoneType.SERVICE)
+                && "org.ametys.plugins.calendar.Agenda".equals(zoneItem.getServiceId());
+    }
+}
Index: webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsGenerator.java
===================================================================
--- webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsGenerator.java	(revision 25541)
+++ webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsGenerator.java	(working copy)
@@ -22,13 +22,25 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.GregorianCalendar;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
+import org.ametys.cms.filter.ContentFilterExtensionPoint;
+import org.ametys.cms.repository.Content;
+import org.ametys.plugins.repository.AmetysObjectIterable;
+import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.repository.query.expression.Expression;
+import org.ametys.runtime.util.I18nizableText;
+import org.ametys.runtime.util.IgnoreRootHandler;
+import org.ametys.web.filter.ContentFilterHelper;
+import org.ametys.web.filter.WebContentFilter;
+import org.ametys.web.filter.WebContentFilter.FilterSearchContext;
+import org.ametys.web.repository.content.WebContent;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.page.ZoneItem;
+import org.ametys.web.tags.Tag;
+import org.ametys.web.tags.TagCategory;
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.cocoon.ProcessingException;
@@ -46,44 +58,11 @@
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
-import org.ametys.cms.filter.ContentFilterExtensionPoint;
-import org.ametys.cms.repository.Content;
-import org.ametys.plugins.repository.AmetysObjectIterable;
-import org.ametys.plugins.repository.AmetysObjectResolver;
-import org.ametys.plugins.repository.query.expression.AndExpression;
-import org.ametys.plugins.repository.query.expression.DateExpression;
-import org.ametys.plugins.repository.query.expression.Expression;
-import org.ametys.plugins.repository.query.expression.Expression.Operator;
-import org.ametys.plugins.repository.query.expression.MetadataExpression;
-import org.ametys.plugins.repository.query.expression.NotExpression;
-import org.ametys.plugins.repository.query.expression.OrExpression;
-import org.ametys.runtime.util.I18nizableText;
-import org.ametys.runtime.util.IgnoreRootHandler;
-import org.ametys.web.filter.ContentFilterHelper;
-import org.ametys.web.filter.WebContentFilter;
-import org.ametys.web.filter.WebContentFilter.FilterSearchContext;
-import org.ametys.web.repository.content.WebContent;
-import org.ametys.web.repository.page.Page;
-import org.ametys.web.repository.page.ZoneItem;
-import org.ametys.web.tags.Tag;
-import org.ametys.web.tags.TagCategory;
-import org.ametys.web.tags.TagProviderExtensionPoint;
-
 /**
  * Query and generate news according to many parameters.
  */
 public class EventsGenerator extends ServiceableGenerator
 {
-
-    /** The events filter ID. */
-    public static final String EVENTS_FILTER_ID = "org.ametys.plugins.calendar.events.EventsFilter";
-
-    /** The start date metadata. */
-    public static final String START_DATE_META = "start-date";
-
-    /** The end date metadata. */
-    public static final String END_DATE_META = "end-date";
-
     /** The source resolver. */
     protected SourceResolver _resolver;
 
@@ -95,9 +74,9 @@
 
     /** The content filter helper. */
     protected ContentFilterHelper _filterHelper;
-
-    /** The tag provider extension point. */
-    protected TagProviderExtensionPoint _tagProviderEP;
+    
+    /** The events helper */
+    protected EventsFilterHelper _eventsFilterHelper;
 
     @Override
     public void service(ServiceManager serviceManager) throws ServiceException
@@ -107,7 +86,7 @@
         _filterExtPt = (ContentFilterExtensionPoint) serviceManager.lookup(ContentFilterExtensionPoint.ROLE);
         _filterHelper = (ContentFilterHelper) serviceManager.lookup(ContentFilterHelper.ROLE);
         _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
-        _tagProviderEP = (TagProviderExtensionPoint) serviceManager.lookup(TagProviderExtensionPoint.ROLE);
+        _eventsFilterHelper = (EventsFilterHelper) serviceManager.lookup(EventsFilterHelper.ROLE);
     }
 
     @Override
@@ -139,6 +118,8 @@
         {
             singleTag = singleTag.toUpperCase();
         }
+        
+        
         Page page = (Page) request.getAttribute(Page.class.getName());
         // Get the zone item, as a request attribute or from the ID in the
         // parameters.
@@ -149,27 +130,28 @@
             zoneItemId = URLDecoder.decode(zoneItemId, "UTF-8");
             zoneItem = (ZoneItem) _ametysResolver.resolveById(zoneItemId);
         }
+        
         DateTime dateTime = new DateTime(year, month, day, 0, 0, 0, 0);
-        String title = getTitle(zoneItem);
-        String rangeType = parameters.getParameter("rangeType", getDefaultRangeType(zoneItem));
-        boolean maskOrphan = getMaskOrphan(zoneItem);
-        boolean pdfDownload = getPdfDownload(zoneItem);
-        boolean icalDownload = getIcalDownload(zoneItem);
-        String link = getLink(zoneItem);
-        String linkTitle = getLinkTitle(zoneItem);
+        String title = _eventsFilterHelper.getTitle(zoneItem);
+        String rangeType = parameters.getParameter("rangeType", _eventsFilterHelper.getDefaultRangeType(zoneItem));
+        boolean maskOrphan = _eventsFilterHelper.getMaskOrphan(zoneItem);
+        boolean pdfDownload = _eventsFilterHelper.getPdfDownload(zoneItem);
+        boolean icalDownload = _eventsFilterHelper.getIcalDownload(zoneItem);
+        String link = _eventsFilterHelper.getLink(zoneItem);
+        String linkTitle = _eventsFilterHelper.getLinkTitle(zoneItem);
         
         boolean doRetrieveView = !StringUtils.equalsIgnoreCase("false", parameters.getParameter("do-retrieve-view", "true"));
 
         // Get the content types to match
-        Set<String> contentTypes = _getContentTypes(zoneItem);
+        Set<String> contentTypes = _eventsFilterHelper.getContentTypes(zoneItem);
 
         // Get the tags to match, from the zone item or from the parameters.
-        Set<String> tags = _getTags(zoneItem);
+        Set<String> tags = _eventsFilterHelper.getTags(zoneItem, parameters.getParameter("tags", null));
         // Get the categories of the tags to match, from the zone item or from
         // the parameters.
-        Set<TagCategory> categories = _getTagCategories(zoneItem, siteName);
+        Set<TagCategory> categories = _eventsFilterHelper.getTagCategories(zoneItem, siteName, parameters.getParameter("tag-categories", null));
         // Get all tags belonging to the categories.
-        Set<String> categoryTags = getAllTags(categories);
+        Set<String> categoryTags = _eventsFilterHelper.getAllTags(categories);
         // Restrain the list to a single tag if provided.
         if (categoryTags.contains(singleTag) && StringUtils.isNotEmpty(singleTag) && !"all".equals(singleTag))
         {
@@ -179,15 +161,15 @@
         String pagePath = page != null ? page.getPathInSitemap() : "";
 
         // Get the declared events filter.
-        EventsFilter eventsFilter = (EventsFilter) _filterExtPt.getExtension(EVENTS_FILTER_ID);
+        EventsFilter eventsFilter = (EventsFilter) _filterExtPt.getExtension(EventsFilterHelper.EVENTS_FILTER_ID);
 
         // Get the date range and deduce the expression (single day or
         // month-before to month-after).
-        DateRange dateRange = _getDateRange(type, year, month, day, monthsBefore, monthsAfter, rangeType);
-        Expression expression = _getExpression(type, dateRange);
+        EventsFilterHelper.DateRange dateRange = _eventsFilterHelper.getDateRange(type, year, month, day, monthsBefore, monthsAfter, rangeType);
+        Expression expression = _eventsFilterHelper.getExpression(type, dateRange);
 
         // Configure the filter to return the wanted contents.
-        configureFilter(eventsFilter, expression, contentTypes, tags, categoryTags, view, maskOrphan);
+        _eventsFilterHelper.configureFilter(eventsFilter, expression, contentTypes, tags, categoryTags, view, maskOrphan);
 
         // Get the corresponding contents.
         AmetysObjectIterable<Content> eventContents = eventsFilter.getMatchingContents(siteName, lang, page);
@@ -234,6 +216,8 @@
         contentHandler.startDocument();
         XMLUtils.startElement(contentHandler, "events", atts);
 
+        saxRssUrl(zoneItem, monthsBefore, monthsAfter);
+
         // Generate months (used in calendar mode) and days (used in full-page
         // agenda mode).
         _saxMonths(dateRange);
@@ -256,6 +240,19 @@
         contentHandler.endDocument();
     }
 
+
+    private void saxRssUrl(ZoneItem zoneItem, int monthsBefore, int monthsAfter) throws SAXException 
+    {
+    	if (zoneItem != null && zoneItem.getServiceParameters().getBoolean("rss", false))
+        {
+            // Split protocol and id
+            String[] zoneItemId = zoneItem.getId().split("://");
+            String url = "_plugins/calendar/" + zoneItemId[1] + "/rss.xml";
+            
+            XMLUtils.createElement(contentHandler, "rssUrl", url);
+        }
+	}
+    
     /**
      * SAX all contents matching the given filter
      * 
@@ -272,10 +269,10 @@
 
         for (Content content : contents)
         {
-            if (!maskOrphan || (maskOrphan && !isOrphan(content)))
+            if (!maskOrphan || (maskOrphan && !_eventsFilterHelper.isOrphan(content)))
             {
-                Date start = content.getMetadataHolder().getDate(START_DATE_META, null);
-                Date end = content.getMetadataHolder().getDate(END_DATE_META, null);
+                Date start = content.getMetadataHolder().getDate(EventsFilterHelper.START_DATE_META, null);
+                Date end = content.getMetadataHolder().getDate(EventsFilterHelper.END_DATE_META, null);
 
                 List<String> params = new ArrayList<String>();
                 params.add(content.getTitle());
@@ -362,56 +359,12 @@
     }
 
     /**
-     * Configure the filter to return the wanted contents.
-     * 
-     * @param eventsFilter the events filter.
-     * @param expression the metadata Expression, can be null.
-     * @param contentTypes a list of content types that will restrict the scope of the request.
-     * @param andTags a list of tags that are all required for the content to
-     *            match.
-     * @param orTags a list of tags among which only one is required for the
-     *            content to match.
-     * @param view the view.
-     * @param maskOrphan true to prevent getting orphan contents.
-     */
-    protected void configureFilter(EventsFilter eventsFilter, Expression expression, Collection<String> contentTypes, Collection<String> andTags, Set<String> orTags, String view, boolean maskOrphan)
-    {
-        // Set the content types expression, even if null
-        eventsFilter.getContentTypes().clear();
-        for (String contentType : contentTypes)
-        {
-            eventsFilter.addContentType(contentType);
-        }
-
-        // Set the metadata expression, even if null.
-        eventsFilter.setMetadataExpression(expression);
-
-        if (StringUtils.isNotEmpty(view))
-        {
-            eventsFilter.setView(view);
-        }
-
-        for (FilterSearchContext filterContext : eventsFilter.getSearchContexts())
-        {
-            filterContext.getTags().clear();
-            for (String tag : andTags)
-            {
-                filterContext.addTag(tag);
-            }
-        }
-
-        eventsFilter.setOrTags(orTags);
-
-        eventsFilter.setMaskOrphanContents(maskOrphan);
-    }
-
-    /**
      * SAX information on the months spanning the date range.
      * 
      * @param dateRange the date range.
      * @throws SAXException
      */
-    protected void _saxMonths(DateRange dateRange) throws SAXException
+    protected void _saxMonths(EventsFilterHelper.DateRange dateRange) throws SAXException
     {
         if (dateRange != null && dateRange.getStartDate() != null && dateRange.getEndDate() != null)
         {
@@ -451,7 +404,7 @@
      * @param rangeType the range type, "month" or "week".
      * @throws SAXException
      */
-    protected void _saxDaysNew(DateRange dateRange, String rangeType) throws SAXException
+    protected void _saxDaysNew(EventsFilterHelper.DateRange dateRange, String rangeType) throws SAXException
     {
         XMLUtils.startElement(contentHandler, "calendar-months");
 
@@ -561,7 +514,7 @@
 
             // Break on week on the last day of the week (but not on the last
             // week).
-            if (dayCal.get(Calendar.DAY_OF_WEEK) == getLastDayOfWeek(dayCal) && days.hasNext())
+            if (dayCal.get(Calendar.DAY_OF_WEEK) == _eventsFilterHelper.getLastDayOfWeek(dayCal) && days.hasNext())
             {
                 previousWeekDay = day.minusDays(6);
                 nextWeekDay = day.plusDays(8);
@@ -637,7 +590,7 @@
             category.getTitle().toSAX(contentHandler, "title");
 
             XMLUtils.startElement(contentHandler, "tags");
-            for (Tag tag : getAllTags(category))
+            for (Tag tag : _eventsFilterHelper.getAllTags(category))
             {
                 AttributesImpl tagAttrs = new AttributesImpl();
                 tagAttrs.addCDATAAttribute("name", tag.getName());
@@ -654,583 +607,6 @@
         XMLUtils.endElement(contentHandler, "tag-categories");
     }
 
-    /**
-     * Get the service title.
-     * 
-     * @param zoneItem the zone item.
-     * @return the service title.
-     */
-    protected String getTitle(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getString("title", "");
-    }
 
-    /**
-     * Get the link.
-     * 
-     * @param zoneItem the zone item.
-     * @return the link.
-     */
-    protected String getLink(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getString("link", "");
-    }
-
-    /**
-     * Get the link title.
-     * 
-     * @param zoneItem the zone item.
-     * @return the link title.
-     */
-    protected String getLinkTitle(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getString("link-title", "");
-    }
-
-    /**
-     * Get the default range type parameter value.
-     * 
-     * @param zoneItem the zone item.
-     * @return the default range type.
-     */
-    protected String getDefaultRangeType(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getString("default-range", "month");
-    }
-
-    /**
-     * Mask orphan?
-     * 
-     * @param zoneItem the zone item.
-     * @return mask orphan.
-     */
-    protected boolean getMaskOrphan(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getBoolean("mask-orphan", false);
-    }
-
-    /**
-     * Is there a PDF download link on the service?
-     * 
-     * @param zoneItem the zone item.
-     * @return true if a PDF download link will be present, false otherwise.
-     */
-    protected boolean getPdfDownload(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getBoolean("pdf-download", false);
-    }
-
-    /**
-     * Is there an iCalendar export link on the service?
-     * 
-     * @param zoneItem the zone item.
-     * @return true if an iCalendar export link will be present, false
-     *         otherwise.
-     */
-    protected boolean getIcalDownload(ZoneItem zoneItem)
-    {
-        return zoneItem.getServiceParameters().getBoolean("ical-download", false);
-    }
-
-    /**
-     * Get the content types through the service parameters.
-     * 
-     * @param zoneItem the zone item.
-     * @return the content types.
-     */
-    protected Set<String> _getContentTypes(ZoneItem zoneItem)
-    {
-        Set<String> contentTypes = new LinkedHashSet<String>();
-        
-        String[] cTypes = zoneItem.getServiceParameters().getString("content-types").split(",");
-        if (cTypes.length > 0 && cTypes[0].length() > 0)
-        {
-            for (String cType : cTypes)
-            {
-                if (!"".equals(cType))
-                {
-                    contentTypes.add(cType);
-                }
-            }
-        }
-
-        return contentTypes;
-    }
-
-    /**
-     * Get the tags of a zone item.
-     * 
-     * @param zoneItem the zone item.
-     * @return the tags.
-     */
-    protected Set<String> _getTags(ZoneItem zoneItem)
-    {
-        Set<String> tags = new LinkedHashSet<String>();
-
-        String tagAsStr = parameters.getParameter("tags", null);
-
-        if (tagAsStr == null)
-        {
-            tagAsStr = zoneItem.getServiceParameters().getString("tags", "");
-        }
-
-        String[] tagsArray = StringUtils.isNotEmpty(tagAsStr) ? StringUtils.split(tagAsStr, ',') : new String[] {};
-        for (int i = 0; i < tagsArray.length; i++)
-        {
-            tags.add(tagsArray[i]);
-        }
-
-        return tags;
-    }
-
-    /**
-     * Get the selected tags categories.
-     * 
-     * @param zoneItem the zone item.
-     * @param siteName the site name.
-     * @return the tags categories.
-     */
-    protected Set<TagCategory> _getTagCategories(ZoneItem zoneItem, String siteName)
-    {
-        Set<TagCategory> categories = new LinkedHashSet<TagCategory>();
-
-        String categoriesAsStr = parameters.getParameter("tag-categories", null);
-
-        if (categoriesAsStr == null)
-        {
-            categoriesAsStr = zoneItem.getServiceParameters().getString("tag-categories", "");
-        }
-
-        String[] categoriesArray = StringUtils.isNotEmpty(categoriesAsStr) ? StringUtils.split(categoriesAsStr, ',') : new String[] {};
-        for (int i = 0; i < categoriesArray.length; i++)
-        {
-            TagCategory category = _tagProviderEP.getCategory(siteName, categoriesArray[i]);
-            if (category != null)
-            {
-                categories.add(category);
-            }
-        }
-
-        return categories;
-    }
-
-    /**
-     * Get all the tags belonging to a collection of categories.
-     * 
-     * @param categories the collection of tag categories.
-     * @return an exhaustive set of the tags.
-     */
-    protected Set<String> getAllTags(Collection<TagCategory> categories)
-    {
-        Set<String> allTags = new LinkedHashSet<String>();
-
-        for (TagCategory category : categories)
-        {
-            Map<String, Tag> tags = category.getTags();
-            Map<String, TagCategory> subCategories = category.getCategories();
-
-            if (tags != null)
-            {
-                for (Tag tag : tags.values())
-                {
-                    allTags.add(tag.getName());
-                }
-            }
-            if (subCategories != null)
-            {
-                allTags.addAll(getAllTags(subCategories.values()));
-            }
-        }
-
-        return allTags;
-    }
-
-    /**
-     * Get all the tags belonging to a single category.
-     * 
-     * @param category the category.
-     * @return an exhaustive set of the tags.
-     */
-    protected Set<Tag> getAllTags(TagCategory category)
-    {
-        Set<Tag> allTags = new LinkedHashSet<Tag>();
-
-        Map<String, Tag> tags = category.getTags();
-        Map<String, TagCategory> subCategories = category.getCategories();
-
-        if (tags != null)
-        {
-            allTags.addAll(tags.values());
-        }
-
-        if (subCategories != null)
-        {
-            for (TagCategory subCategory : subCategories.values())
-            {
-                allTags.addAll(getAllTags(subCategory));
-            }
-        }
-
-        return allTags;
-    }
-
-    /**
-     * Get the date range from the calendar type and parameters.
-     * 
-     * @param type the calendar mode: "calendar", "single-day" or "agenda".
-     * @param year the year.
-     * @param month the month.
-     * @param day the day.
-     * @param monthsBefore get x months before.
-     * @param monthsAfter get x months after.
-     * @param rangeType the range type, "month" or "week".
-     * @return the date range.
-     */
-    protected DateRange _getDateRange(String type, int year, int month, int day, int monthsBefore, int monthsAfter, String rangeType)
-    {
-        DateRange dateRange = null;
-
-        // Single day mode.
-        if ("single-day".equals(type))
-        {
-            Calendar date = _getDate(year, month, day);
-            dateRange = new DateRange(date.getTime(), date.getTime());
-        }
-        // JS calendar mode.
-        else if ("calendar".equals(type) || "agenda".equals(type))
-        {
-            dateRange = _getDateRange(monthsBefore, monthsAfter);
-        }
-        else if ("period".equals(type))
-        {
-            Calendar date = _getDate(year, month, day);
-
-            dateRange = _getDateRange(date, monthsBefore, monthsAfter);
-        }
-
-        if (getLogger().isInfoEnabled() && dateRange != null)
-        {
-            String start = ISODateTimeFormat.dateTime().print(new DateTime(dateRange.getStartDate()));
-            String end = ISODateTimeFormat.dateTime().print(new DateTime(dateRange.getEndDate()));
-            getLogger().info("Getting contents between " + start + " and " + end);
-        }
-
-        return dateRange;
-    }
-
-    /**
-     * Get a date range from a period (x months before now and x months after).
-     * 
-     * @param monthsBefore the wanted number of months before the current month.
-     * @param monthsAfter the wanted number of months after the current month.
-     * @return the date range.
-     */
-    protected DateRange _getDateRange(int monthsBefore, int monthsAfter)
-    {
-        return _getDateRange(new GregorianCalendar(), monthsBefore, monthsAfter);
-    }
-
-    /**
-     * Get a date range from a period, around a given date (x months before and
-     * x months after the date).
-     * 
-     * @param date the date.
-     * @param monthsBefore the wanted number of months before the current month.
-     * @param monthsAfter the wanted number of months after the current month.
-     * @return the date range.
-     */
-    protected DateRange _getDateRange(Calendar date, int monthsBefore, int monthsAfter)
-    {
-        DateRange dateRange = new DateRange();
-
-        Calendar start = (Calendar) date.clone();
-        Calendar end = null;
-
-        start.set(Calendar.HOUR_OF_DAY, 0);
-        start.set(Calendar.MINUTE, 0);
-        start.set(Calendar.SECOND, 0);
-        start.set(Calendar.MILLISECOND, 0);
-        start.set(Calendar.DAY_OF_MONTH, 1);
-
-        end = (Calendar) start.clone();
-
-        // Subtract the wanted number of months before.
-        start.add(Calendar.MONTH, 0 - monthsBefore);
-
-        // Add the wanted number of months after.
-        end.add(Calendar.MONTH, monthsAfter + 1);
-
-        dateRange.setStartDate(start.getTime());
-        dateRange.setEndDate(end.getTime());
-
-        return dateRange;
-    }
-
-    /**
-     * Get a date range from a date and a range type.
-     * 
-     * @param date the date around which the range is to be.
-     * @param rangeType the range type, "month" or "week".
-     * @return the date range.
-     */
-    protected DateRange _getDateRange(Calendar date, String rangeType)
-    {
-        // Remove the "time" part.
-        DateTime dateTime = new DateTime(date).withMillisOfDay(0);
-        DateTime start = null;
-        DateTime end = null;
-
-        if ("month".equals(rangeType))
-        {
-            // First day of month.
-            start = dateTime.withDayOfMonth(1);
-            // First day of next month.
-            end = start.plusMonths(1);
-        }
-        else
-        {
-            // First day of week.
-            start = dateTime.withDayOfWeek(1);
-            // First day of next week.
-            end = start.plusWeeks(1);
-        }
-
-        return new DateRange(start.toDate(), end.toDate());
-    }
-
-    /**
-     * Get a date object from a year-month-day set.
-     * 
-     * @param year the year.
-     * @param month the month (1 to 12).
-     * @param day the day (1 to 31).
-     * @return the date object.
-     */
-    protected Calendar _getDate(int year, int month, int day)
-    {
-        Calendar cal = new GregorianCalendar();
-
-        cal.set(Calendar.HOUR_OF_DAY, 0);
-        cal.set(Calendar.MINUTE, 0);
-        cal.set(Calendar.SECOND, 0);
-        cal.set(Calendar.MILLISECOND, 0);
-        if (year > 0 && month > 0 && day > 0)
-        {
-            cal.set(Calendar.DAY_OF_MONTH, day);
-            cal.set(Calendar.MONTH, month - 1);
-            cal.set(Calendar.YEAR, year);
-        }
-
-        return cal;
-    }
-
-    /**
-     * Get the metadata expression from the calendar type and date range.
-     * 
-     * @param type the calendar type, may be "calendar", "single-day" or
-     *            "agenda".
-     * @param dateRange the date range. Only the start date is used for
-     *            "single-day" type.
-     * @return the metadata Expression on contents.
-     */
-    protected Expression _getExpression(String type, DateRange dateRange)
-    {
-        Expression expression = null;
-
-        if (dateRange != null)
-        {
-            if ("single-day".equals(type))
-            {
-                expression = _getMetadataExpression(dateRange.getStartDate());
-            }
-            else if ("calendar".equals(type))
-            {
-                expression = _getMetadataExpression(dateRange);
-            }
-            else if ("agenda".equals(type))
-            {
-                expression = _getMetadataExpression(dateRange);
-            }
-            else if ("period".equals(type))
-            {
-                expression = _getMetadataExpression(dateRange);
-            }
-        }
-
-        return expression;
-    }
-
-    /**
-     * Get a metadata expression from a date range.
-     * 
-     * @param dateRange the date range.
-     * @return the date range metadata expression.
-     */
-    protected Expression _getMetadataExpression(DateRange dateRange)
-    {
-        Expression expression = null;
-
-        if (dateRange != null)
-        {
-            Expression startExpr = dateRange.getEndDate() != null ? new DateExpression(START_DATE_META, Operator.LT, dateRange.getEndDate()) : null;
-
-            Expression fullEndExpr = null;
-            if (dateRange.getStartDate() != null)
-            {
-                Expression endExpr = new DateExpression(END_DATE_META, Operator.GE, dateRange.getStartDate());
-                Expression startAfterQueryBeginExpr = new DateExpression(START_DATE_META, Operator.GE, dateRange.getStartDate());
-                Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META));
-                fullEndExpr = new AndExpression(noEndDate, startAfterQueryBeginExpr);
-                fullEndExpr = new OrExpression(endExpr, fullEndExpr);
-            }
-
-            if (dateRange.getStartDate() == null && dateRange.getEndDate() != null && startExpr != null)
-            {
-                expression = startExpr;
-            }
-            else if (dateRange.getStartDate() != null && dateRange.getEndDate() == null && fullEndExpr != null)
-            {
-                expression = fullEndExpr;
-            }
-            else
-            {
-                expression = new AndExpression(startExpr, fullEndExpr);
-            }
-        }
-
-        return expression;
-    }
-
-    /**
-     * Get a metadata expression from a single date.
-     * 
-     * @param date the date.
-     * @return the date metadata expression.
-     */
-    protected Expression _getMetadataExpression(Date date)
-    {
-        Expression expression = null;
-        
-        if (date != null)
-        {
-            Date nextDay = new DateTime(date.getTime()).plusDays(1).toDate();
-            
-            Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META));
-            Expression startDate = new DateExpression(START_DATE_META, Operator.GE, date);
-            Expression startDateNextDay = new DateExpression(START_DATE_META, Operator.LT, nextDay);
-            Expression onlyStartDate = new AndExpression(noEndDate, startDate, startDateNextDay);
-            
-            Expression noStartDate = new NotExpression(new MetadataExpression(START_DATE_META));
-            Expression endDate = new DateExpression(END_DATE_META, Operator.GE, date);
-            Expression endDateNextDay = new DateExpression(END_DATE_META, Operator.LT, nextDay);
-            Expression onlyEndDate = new AndExpression(noStartDate, endDate, endDateNextDay);
-            
-            Expression afterStart = new DateExpression(START_DATE_META, Operator.LT, nextDay);
-            Expression beforeEnd = new DateExpression(END_DATE_META, Operator.GE, date);
-            Expression bothDates = new AndExpression(afterStart, beforeEnd);
-            
-            expression = new OrExpression(onlyStartDate, onlyEndDate, bothDates);
-        }
-        
-        return expression;
-    }
-
-    /**
-     * Get last day of the week at a given date.
-     * 
-     * @param cal the date as a Calendar.
-     * @return the last day of the week.
-     */
-    protected int getLastDayOfWeek(Calendar cal)
-    {
-        return Calendar.SUNDAY;
-    }
-
-    /**
-     * Determines if a content is orphan
-     * 
-     * @param content the content to test
-     * @return true if the content is orphan
-     */
-    protected boolean isOrphan(Content content)
-    {
-        if (content instanceof WebContent)
-        {
-            return ((WebContent) content).getReferencingPages().size() == 0;
-        }
-        return true;
-    }
-
-    /**
-     * Class representing a date range.
-     */
-    public class DateRange
-    {
-        /** The start date. */
-        protected Date _startDate;
-
-        /** The start date. */
-        protected Date _endDate;
-
-        /**
-         * Default constructor.
-         */
-        public DateRange()
-        {
-            this(null, null);
-        }
-
-        /**
-         * Constructor with params.
-         * 
-         * @param startDate
-         * @param endDate
-         */
-        public DateRange(Date startDate, Date endDate)
-        {
-            this._startDate = startDate;
-            this._endDate = endDate;
-        }
-
-        /**
-         * Get the startDate.
-         * 
-         * @return the startDate
-         */
-        public Date getStartDate()
-        {
-            return _startDate;
-        }
-
-        /**
-         * Set the startDate.
-         * 
-         * @param startDate the startDate to set
-         */
-        public void setStartDate(Date startDate)
-        {
-            this._startDate = startDate;
-        }
-
-        /**
-         * Get the endDate.
-         * 
-         * @return the endDate
-         */
-        public Date getEndDate()
-        {
-            return _endDate;
-        }
-
-        /**
-         * Set the endDate.
-         * 
-         * @param endDate the endDate to set
-         */
-        public void setEndDate(Date endDate)
-        {
-            this._endDate = endDate;
-        }
-    }
 
 }
Index: webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsFilterHelper.java
===================================================================
--- webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsFilterHelper.java	(revision 0)
+++ webapp/cms/plugins/calendar/src/org/ametys/plugins/calendar/events/EventsFilterHelper.java	(revision 0)
@@ -0,0 +1,681 @@
+package org.ametys.plugins.calendar.events;
+
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.ametys.cms.repository.Content;
+import org.ametys.plugins.repository.query.expression.AndExpression;
+import org.ametys.plugins.repository.query.expression.DateExpression;
+import org.ametys.plugins.repository.query.expression.Expression;
+import org.ametys.plugins.repository.query.expression.MetadataExpression;
+import org.ametys.plugins.repository.query.expression.NotExpression;
+import org.ametys.plugins.repository.query.expression.OrExpression;
+import org.ametys.plugins.repository.query.expression.Expression.Operator;
+import org.ametys.web.filter.WebContentFilter.FilterSearchContext;
+import org.ametys.web.repository.content.WebContent;
+import org.ametys.web.repository.page.ZoneItem;
+import org.ametys.web.tags.Tag;
+import org.ametys.web.tags.TagCategory;
+import org.ametys.web.tags.TagProviderExtensionPoint;
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.format.ISODateTimeFormat;
+
+public class EventsFilterHelper extends AbstractLogEnabled implements Serviceable, Component
+{
+	/** The avalon role */
+	public static String ROLE = EventsFilterHelper.class.getName();
+
+    /** The start date metadata. */
+    public static final String START_DATE_META = "start-date";
+
+    /** The end date metadata. */
+    public static final String END_DATE_META = "end-date";
+    
+    /** The events filter ID. */
+    public static final String EVENTS_FILTER_ID = "org.ametys.plugins.calendar.events.EventsFilter";
+
+    
+    /** The tag provider extension point. */
+    protected TagProviderExtensionPoint _tagProviderEP;
+	
+	@Override
+	public void service(ServiceManager manager) throws ServiceException 
+	{
+        _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE);
+	}
+	
+    /**
+     * Get the service title.
+     * 
+     * @param zoneItem the zone item.
+     * @return the service title.
+     */
+    protected String getTitle(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getString("title", "");
+    }
+
+    /**
+     * Get the link.
+     * 
+     * @param zoneItem the zone item.
+     * @return the link.
+     */
+    protected String getLink(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getString("link", "");
+    }
+
+    /**
+     * Get the link title.
+     * 
+     * @param zoneItem the zone item.
+     * @return the link title.
+     */
+    protected String getLinkTitle(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getString("link-title", "");
+    }
+
+    /**
+     * Get the default range type parameter value.
+     * 
+     * @param zoneItem the zone item.
+     * @return the default range type.
+     */
+    public String getDefaultRangeType(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getString("default-range", "month");
+    }
+
+    /**
+     * Mask orphan?
+     * 
+     * @param zoneItem the zone item.
+     * @return mask orphan.
+     */
+    public boolean getMaskOrphan(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getBoolean("mask-orphan", false);
+    }
+
+    /**
+     * Is there a PDF download link on the service?
+     * 
+     * @param zoneItem the zone item.
+     * @return true if a PDF download link will be present, false otherwise.
+     */
+    protected boolean getPdfDownload(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getBoolean("pdf-download", false);
+    }
+
+    /**
+     * Is there an iCalendar export link on the service?
+     * 
+     * @param zoneItem the zone item.
+     * @return true if an iCalendar export link will be present, false
+     *         otherwise.
+     */
+    protected boolean getIcalDownload(ZoneItem zoneItem)
+    {
+        return zoneItem.getServiceParameters().getBoolean("ical-download", false);
+    }
+    
+    /**
+     * Get the content types through the service parameters.
+     * 
+     * @param zoneItem the zone item.
+     * @return the content types.
+     */
+    public Set<String> getContentTypes(ZoneItem zoneItem)
+    {
+        Set<String> contentTypes = new LinkedHashSet<String>();
+        
+        String[] cTypes = zoneItem.getServiceParameters().getString("content-types").split(",");
+        if (cTypes.length > 0 && cTypes[0].length() > 0)
+        {
+            for (String cType : cTypes)
+            {
+                if (!"".equals(cType))
+                {
+                    contentTypes.add(cType);
+                }
+            }
+        }
+
+        return contentTypes;
+    }
+
+    /**
+     * Get the tags of a zone item.
+     * 
+     * @param zoneItem the zone item.
+     * @param tagAsStr the tags as string
+     * @return the tags.
+     */
+    public Set<String> getTags(ZoneItem zoneItem, String tagAsStr)
+    {
+        Set<String> tags = new LinkedHashSet<String>();
+
+        if (tagAsStr == null)
+        {
+            tagAsStr = zoneItem.getServiceParameters().getString("tags", "");
+        }
+
+        String[] tagsArray = StringUtils.isNotEmpty(tagAsStr) ? StringUtils.split(tagAsStr, ',') : new String[] {};
+        for (int i = 0; i < tagsArray.length; i++)
+        {
+            tags.add(tagsArray[i]);
+        }
+
+        return tags;
+    }
+
+    /**
+     * Get the selected tags categories.
+     * 
+     * @param zoneItem the zone item.
+     * @param siteName the site name.
+     * @param categoriesAsStr the tag categories
+     * @return the tags categories.
+     */
+    public Set<TagCategory> getTagCategories(ZoneItem zoneItem, String siteName, String categoriesAsStr)
+    {
+        Set<TagCategory> categories = new LinkedHashSet<TagCategory>();
+
+        if (categoriesAsStr == null)
+        {
+            categoriesAsStr = zoneItem.getServiceParameters().getString("tag-categories", "");
+        }
+
+        String[] categoriesArray = StringUtils.isNotEmpty(categoriesAsStr) ? StringUtils.split(categoriesAsStr, ',') : new String[] {};
+        for (int i = 0; i < categoriesArray.length; i++)
+        {
+            TagCategory category = _tagProviderEP.getCategory(siteName, categoriesArray[i]);
+            if (category != null)
+            {
+                categories.add(category);
+            }
+        }
+
+        return categories;
+    }
+
+    /**
+     * Get all the tags belonging to a collection of categories.
+     * 
+     * @param categories the collection of tag categories.
+     * @return an exhaustive set of the tags.
+     */
+    public Set<String> getAllTags(Collection<TagCategory> categories)
+    {
+        Set<String> allTags = new LinkedHashSet<String>();
+
+        for (TagCategory category : categories)
+        {
+            Map<String, Tag> tags = category.getTags();
+            Map<String, TagCategory> subCategories = category.getCategories();
+
+            if (tags != null)
+            {
+                for (Tag tag : tags.values())
+                {
+                    allTags.add(tag.getName());
+                }
+            }
+            if (subCategories != null)
+            {
+                allTags.addAll(getAllTags(subCategories.values()));
+            }
+        }
+
+        return allTags;
+    }
+
+    /**
+     * Get all the tags belonging to a single category.
+     * 
+     * @param category the category.
+     * @return an exhaustive set of the tags.
+     */
+    protected Set<Tag> getAllTags(TagCategory category)
+    {
+        Set<Tag> allTags = new LinkedHashSet<Tag>();
+
+        Map<String, Tag> tags = category.getTags();
+        Map<String, TagCategory> subCategories = category.getCategories();
+
+        if (tags != null)
+        {
+            allTags.addAll(tags.values());
+        }
+
+        if (subCategories != null)
+        {
+            for (TagCategory subCategory : subCategories.values())
+            {
+                allTags.addAll(getAllTags(subCategory));
+            }
+        }
+
+        return allTags;
+    }
+
+    /**
+     * Get the date range from the calendar type and parameters.
+     * 
+     * @param type the calendar mode: "calendar", "single-day" or "agenda".
+     * @param year the year.
+     * @param month the month.
+     * @param day the day.
+     * @param monthsBefore get x months before.
+     * @param monthsAfter get x months after.
+     * @param rangeType the range type, "month" or "week".
+     * @return the date range.
+     */
+    public DateRange getDateRange(String type, int year, int month, int day, int monthsBefore, int monthsAfter, String rangeType)
+    {
+        DateRange dateRange = null;
+
+        // Single day mode.
+        if ("single-day".equals(type))
+        {
+            Calendar date = _getDate(year, month, day);
+            dateRange = new DateRange(date.getTime(), date.getTime());
+        }
+        // JS calendar mode.
+        else if ("calendar".equals(type) || "agenda".equals(type))
+        {
+            dateRange = _getDateRange(monthsBefore, monthsAfter);
+        }
+        else if ("period".equals(type))
+        {
+            Calendar date = _getDate(year, month, day);
+
+            dateRange = _getDateRange(date, monthsBefore, monthsAfter);
+        }
+
+        if (getLogger().isInfoEnabled() && dateRange != null)
+        {
+            String start = ISODateTimeFormat.dateTime().print(new DateTime(dateRange.getStartDate()));
+            String end = ISODateTimeFormat.dateTime().print(new DateTime(dateRange.getEndDate()));
+            getLogger().info("Getting contents between " + start + " and " + end);
+        }
+
+        return dateRange;
+    }
+
+    /**
+     * Get a date range from a period (x months before now and x months after).
+     * 
+     * @param monthsBefore the wanted number of months before the current month.
+     * @param monthsAfter the wanted number of months after the current month.
+     * @return the date range.
+     */
+    protected DateRange _getDateRange(int monthsBefore, int monthsAfter)
+    {
+        return _getDateRange(new GregorianCalendar(), monthsBefore, monthsAfter);
+    }
+
+    /**
+     * Get a date range from a period, around a given date (x months before and
+     * x months after the date).
+     * 
+     * @param date the date.
+     * @param monthsBefore the wanted number of months before the current month.
+     * @param monthsAfter the wanted number of months after the current month.
+     * @return the date range.
+     */
+    protected DateRange _getDateRange(Calendar date, int monthsBefore, int monthsAfter)
+    {
+        DateRange dateRange = new DateRange();
+
+        Calendar start = (Calendar) date.clone();
+        Calendar end = null;
+
+        start.set(Calendar.HOUR_OF_DAY, 0);
+        start.set(Calendar.MINUTE, 0);
+        start.set(Calendar.SECOND, 0);
+        start.set(Calendar.MILLISECOND, 0);
+        start.set(Calendar.DAY_OF_MONTH, 1);
+
+        end = (Calendar) start.clone();
+
+        // Subtract the wanted number of months before.
+        start.add(Calendar.MONTH, 0 - monthsBefore);
+
+        // Add the wanted number of months after.
+        end.add(Calendar.MONTH, monthsAfter + 1);
+
+        dateRange.setStartDate(start.getTime());
+        dateRange.setEndDate(end.getTime());
+
+        return dateRange;
+    }
+
+    /**
+     * Get a date range from a date and a range type.
+     * 
+     * @param date the date around which the range is to be.
+     * @param rangeType the range type, "month" or "week".
+     * @return the date range.
+     */
+    protected DateRange _getDateRange(Calendar date, String rangeType)
+    {
+        // Remove the "time" part.
+        DateTime dateTime = new DateTime(date).withMillisOfDay(0);
+        DateTime start = null;
+        DateTime end = null;
+
+        if ("month".equals(rangeType))
+        {
+            // First day of month.
+            start = dateTime.withDayOfMonth(1);
+            // First day of next month.
+            end = start.plusMonths(1);
+        }
+        else
+        {
+            // First day of week.
+            start = dateTime.withDayOfWeek(1);
+            // First day of next week.
+            end = start.plusWeeks(1);
+        }
+
+        return new DateRange(start.toDate(), end.toDate());
+    }
+
+    /**
+     * Get a date object from a year-month-day set.
+     * 
+     * @param year the year.
+     * @param month the month (1 to 12).
+     * @param day the day (1 to 31).
+     * @return the date object.
+     */
+    protected Calendar _getDate(int year, int month, int day)
+    {
+        Calendar cal = new GregorianCalendar();
+
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        if (year > 0 && month > 0 && day > 0)
+        {
+            cal.set(Calendar.DAY_OF_MONTH, day);
+            cal.set(Calendar.MONTH, month - 1);
+            cal.set(Calendar.YEAR, year);
+        }
+
+        return cal;
+    }
+
+    /**
+     * Get the metadata expression from the calendar type and date range.
+     * 
+     * @param type the calendar type, may be "calendar", "single-day" or
+     *            "agenda".
+     * @param dateRange the date range. Only the start date is used for
+     *            "single-day" type.
+     * @return the metadata Expression on contents.
+     */
+    public Expression getExpression(String type, DateRange dateRange)
+    {
+        Expression expression = null;
+
+        if (dateRange != null)
+        {
+            if ("single-day".equals(type))
+            {
+                expression = _getMetadataExpression(dateRange.getStartDate());
+            }
+            else if ("calendar".equals(type))
+            {
+                expression = _getMetadataExpression(dateRange);
+            }
+            else if ("agenda".equals(type))
+            {
+                expression = _getMetadataExpression(dateRange);
+            }
+            else if ("period".equals(type))
+            {
+                expression = _getMetadataExpression(dateRange);
+            }
+        }
+
+        return expression;
+    }
+
+    /**
+     * Get a metadata expression from a date range.
+     * 
+     * @param dateRange the date range.
+     * @return the date range metadata expression.
+     */
+    protected Expression _getMetadataExpression(DateRange dateRange)
+    {
+        Expression expression = null;
+
+        if (dateRange != null)
+        {
+            Expression startExpr = dateRange.getEndDate() != null ? new DateExpression(START_DATE_META, Operator.LT, dateRange.getEndDate()) : null;
+
+            Expression fullEndExpr = null;
+            if (dateRange.getStartDate() != null)
+            {
+                Expression endExpr = new DateExpression(END_DATE_META, Operator.GE, dateRange.getStartDate());
+                Expression startAfterQueryBeginExpr = new DateExpression(START_DATE_META, Operator.GE, dateRange.getStartDate());
+                Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META));
+                fullEndExpr = new AndExpression(noEndDate, startAfterQueryBeginExpr);
+                fullEndExpr = new OrExpression(endExpr, fullEndExpr);
+            }
+
+            if (dateRange.getStartDate() == null && dateRange.getEndDate() != null && startExpr != null)
+            {
+                expression = startExpr;
+            }
+            else if (dateRange.getStartDate() != null && dateRange.getEndDate() == null && fullEndExpr != null)
+            {
+                expression = fullEndExpr;
+            }
+            else
+            {
+                expression = new AndExpression(startExpr, fullEndExpr);
+            }
+        }
+
+        return expression;
+    }
+
+    /**
+     * Get a metadata expression from a single date.
+     * 
+     * @param date the date.
+     * @return the date metadata expression.
+     */
+    protected Expression _getMetadataExpression(Date date)
+    {
+        Expression expression = null;
+        
+        if (date != null)
+        {
+            Date nextDay = new DateTime(date.getTime()).plusDays(1).toDate();
+            
+            Expression noEndDate = new NotExpression(new MetadataExpression(END_DATE_META));
+            Expression startDate = new DateExpression(START_DATE_META, Operator.GE, date);
+            Expression startDateNextDay = new DateExpression(START_DATE_META, Operator.LT, nextDay);
+            Expression onlyStartDate = new AndExpression(noEndDate, startDate, startDateNextDay);
+            
+            Expression noStartDate = new NotExpression(new MetadataExpression(START_DATE_META));
+            Expression endDate = new DateExpression(END_DATE_META, Operator.GE, date);
+            Expression endDateNextDay = new DateExpression(END_DATE_META, Operator.LT, nextDay);
+            Expression onlyEndDate = new AndExpression(noStartDate, endDate, endDateNextDay);
+            
+            Expression afterStart = new DateExpression(START_DATE_META, Operator.LT, nextDay);
+            Expression beforeEnd = new DateExpression(END_DATE_META, Operator.GE, date);
+            Expression bothDates = new AndExpression(afterStart, beforeEnd);
+            
+            expression = new OrExpression(onlyStartDate, onlyEndDate, bothDates);
+        }
+        
+        return expression;
+    }
+
+    /**
+     * Get last day of the week at a given date.
+     * 
+     * @param cal the date as a Calendar.
+     * @return the last day of the week.
+     */
+    protected int getLastDayOfWeek(Calendar cal)
+    {
+        return Calendar.SUNDAY;
+    }
+
+    /**
+     * Determines if a content is orphan
+     * 
+     * @param content the content to test
+     * @return true if the content is orphan
+     */
+    protected boolean isOrphan(Content content)
+    {
+        if (content instanceof WebContent)
+        {
+            return ((WebContent) content).getReferencingPages().size() == 0;
+        }
+        return true;
+    }
+    
+    /**
+     * Configure the filter to return the wanted contents.
+     * 
+     * @param eventsFilter the events filter.
+     * @param expression the metadata Expression, can be null.
+     * @param contentTypes a list of content types that will restrict the scope of the request.
+     * @param andTags a list of tags that are all required for the content to
+     *            match.
+     * @param orTags a list of tags among which only one is required for the
+     *            content to match.
+     * @param view the view.
+     * @param maskOrphan true to prevent getting orphan contents.
+     */
+    public void configureFilter(EventsFilter eventsFilter, Expression expression, Collection<String> contentTypes, Collection<String> andTags, Set<String> orTags, String view, boolean maskOrphan)
+    {
+        // Set the content types expression, even if null
+    	if (eventsFilter.getContentTypes() != null)
+    	{
+    		eventsFilter.getContentTypes().clear();
+    	}
+        for (String contentType : contentTypes)
+        {
+            eventsFilter.addContentType(contentType);
+        }
+
+        // Set the metadata expression, even if null.
+        eventsFilter.setMetadataExpression(expression);
+
+        if (StringUtils.isNotEmpty(view))
+        {
+            eventsFilter.setView(view);
+        }
+
+        for (FilterSearchContext filterContext : eventsFilter.getSearchContexts())
+        {
+            filterContext.getTags().clear();
+            for (String tag : andTags)
+            {
+                filterContext.addTag(tag);
+            }
+        }
+
+        eventsFilter.setOrTags(orTags);
+
+        eventsFilter.setMaskOrphanContents(maskOrphan);
+    }
+
+    /**
+     * Class representing a date range.
+     */
+    public class DateRange
+    {
+        /** The start date. */
+        protected Date _startDate;
+
+        /** The start date. */
+        protected Date _endDate;
+
+        /**
+         * Default constructor.
+         */
+        public DateRange()
+        {
+            this(null, null);
+        }
+
+        /**
+         * Constructor with params.
+         * 
+         * @param startDate
+         * @param endDate
+         */
+        public DateRange(Date startDate, Date endDate)
+        {
+            this._startDate = startDate;
+            this._endDate = endDate;
+        }
+
+        /**
+         * Get the startDate.
+         * 
+         * @return the startDate
+         */
+        public Date getStartDate()
+        {
+            return _startDate;
+        }
+
+        /**
+         * Set the startDate.
+         * 
+         * @param startDate the startDate to set
+         */
+        public void setStartDate(Date startDate)
+        {
+            this._startDate = startDate;
+        }
+
+        /**
+         * Get the endDate.
+         * 
+         * @return the endDate
+         */
+        public Date getEndDate()
+        {
+            return _endDate;
+        }
+
+        /**
+         * Set the endDate.
+         * 
+         * @param endDate the endDate to set
+         */
+        public void setEndDate(Date endDate)
+        {
+            this._endDate = endDate;
+        }
+    }
+}
Index: webapp/cms/plugins/calendar/i18n/messages_fr.xml
===================================================================
--- webapp/cms/plugins/calendar/i18n/messages_fr.xml	(revision 25541)
+++ webapp/cms/plugins/calendar/i18n/messages_fr.xml	(working copy)
@@ -87,6 +87,9 @@
     <message key="CALENDAR_SERVICE_AGENDA_XSLT_DESC">Rendu à utiliser pour l'affichage du service</message>
     <message key="CALENDAR_SERVICE_AGENDA_XSLT_AGENDA13_LABEL">Agenda pleine page</message>
     
+    <message key="CALENDAR_SERVICE_AGENDA_RSS_FEED">Fils RSS</message>
+	<message key="CALENDAR_SERVICE_AGENDA_RSS_FEED_DESC">Cocher la case pour afficher l'icone de flux RSS</message>
+    
     <!-- Agenda UI -->
     <message key="CALENDAR_SERVICE_AGENDA_FROM_TO">Du {0} au {1}</message>
     <message key="CALENDAR_SERVICE_AGENDA_EVENT_TITLE_FROM_TO">{0} - du {1} au {2}</message>
Index: webapp/cms/plugins/calendar/i18n/messages_en.xml
===================================================================
--- webapp/cms/plugins/calendar/i18n/messages_en.xml	(revision 25541)
+++ webapp/cms/plugins/calendar/i18n/messages_en.xml	(working copy)
@@ -46,6 +46,9 @@
     <message key="CALENDAR_SERVICE_CALENDAR_XSLT_LIST_LABEL">Ametys News 1.2 compatibility mode</message>
     <message key="CALENDAR_SERVICE_CALENDAR_XSLT_LIST13_LABEL">Default</message>
     
+	<message key="CALENDAR_SERVICE_AGENDA_RSS_FEED">RSS feed</message>
+	<message key="CALENDAR_SERVICE_AGENDA_RSS_FEED_DESC">Check box to display an RSS feed</message>
+    
     <!-- Calendar UI -->
     <message key="CALENDAR_SERVICE_CALENDAR_EVENTS_COUNT">Events:</message>
     <message key="CALENDAR_SERVICE_CALENDAR_DAY_EVENTS_TITLE">Events of {0}</message>
Index: webapp/cms/plugins/calendar/sitemap.xmap
===================================================================
--- webapp/cms/plugins/calendar/sitemap.xmap	(revision 25541)
+++ webapp/cms/plugins/calendar/sitemap.xmap	(working copy)
@@ -19,10 +19,13 @@
     <map:components>
         <map:generators>
             <map:generator name="events" src="org.ametys.plugins.calendar.events.EventsGenerator" logger="org.ametys.plugins.calendar.events.EventsGenerator"/>
+            <map:generator name="rss" src="org.ametys.web.filter.RSSGenerator" label="xml" pool-grow="2" pool-max="16" pool-min="0" logger="org.ametys.web.filter.RSSGenerator"/>
         </map:generators>
         
         <map:actions>
             <map:action name="get-site" src="org.ametys.web.content.GetSiteAction" logger="org.ametys.web.content.GetSiteAction"/>
+            <map:action name="set-header" src="org.apache.cocoon.acting.HttpHeaderAction"/>
+            <map:action name="set-content-filter" src="org.ametys.plugins.calendar.actions.SetFilterInRequestAttributesAction"/>	
         </map:actions>
         
         <map:serializers>
@@ -72,6 +75,31 @@
                 <map:serialize type="xhtml"/>
             </map:match>
             
+            <map:match pattern="*/rss.xml">
+				<map:act type="set-header">
+                    <map:parameter name="X-Ametys-Cacheable" value="true"/>
+                </map:act>
+				<map:act type="set-content-filter">
+					<map:parameter name="zoneItemId" value="{1}"/>
+					<map:parameter name="view" value="abstract"/>
+						
+					<map:generate type="rss">
+						<map:parameter name="siteName" value="{siteName}"/>
+						<map:parameter name="lang" value="{lang}"/>
+						<map:parameter name="path" value="{path}"/>
+					</map:generate>
+					<map:transform src="plugin:web://pages/rss/rss.xsl">
+						<map:parameter name="siteURL" value="{uri-prefix:absolute-uri-prefix:{siteName}}"></map:parameter>
+                        <map:parameter name="rssPath" value="_plugins/calendar/{../1}{../2}/rss.xml"></map:parameter>
+					</map:transform>
+					<map:serialize type="xml" mime-type="application/rss+xml"/>
+				</map:act>
+			</map:match>
+			
+			<map:match pattern="_content.rss">
+				 <map:redirect-to uri="cocoon://_plugins/web/_content.rss"/>
+			</map:match>
+            
             <!-- year/month/day/zoneItemId/events.html -->
             <map:match pattern="*/*/*/*/events*.html" type="zoneitem">
                 <map:parameter name="zoneItemIndex" value="4"/>
Index: webapp/cms/plugins/calendar/pages/services/agenda/agenda_1.3.xsl
===================================================================
--- webapp/cms/plugins/calendar/pages/services/agenda/agenda_1.3.xsl	(revision 25541)
+++ webapp/cms/plugins/calendar/pages/services/agenda/agenda_1.3.xsl	(working copy)
@@ -38,6 +38,7 @@
     <xsl:variable name="pdfDownload" select="/events/@pdf-download"/>
     <xsl:variable name="icalDownload" select="/events/@ical-download"/>
     <xsl:variable name="link" select="/events/@link"/>
+    <xsl:variable name="rssUrl" select="/events/rssUrl"/>
     <xsl:variable name="linkTitle" select="/events/@link-title"/>
     <xsl:variable name="rangeType" select="/events/@range"/>
     <xsl:variable name="singleTag" select="/events/@single-tag"/>
@@ -120,6 +121,8 @@
          + -->
     
     <xsl:template name="common-service-body-nonempty-content-title">
+    	<xsl:call-template name="rss"/>
+    	
         <xsl:call-template name="common-service-body-nonempty-content-title-content">
             <xsl:with-param name="title" select="string(/events/@title)"/>
         </xsl:call-template>    
@@ -131,6 +134,15 @@
         <xsl:call-template name="events-marker-when-js"/>
     </xsl:template>
     
+      <!-- Template for the rss link -->
+    <xsl:template name="rss">
+        <xsl:if test="$rssUrl != ''">
+            <div class="rss">
+                <a href="{$site-uri-prefix}/{$rssUrl}"><i18n:text i18n:key="PLUGINS_WEB_SERVICE_FILTERED_CONTENTS_RSS_FEED_ALT" i18n:catalogue="plugin.web"/></a>
+            </div>
+        </xsl:if>    
+    </xsl:template> 
+    
     <!-- +
          | Generating events list when js is missing
          + -->
Index: webapp/cms/plugins/calendar/plugin.xml
===================================================================
--- webapp/cms/plugins/calendar/plugin.xml	(revision 25541)
+++ webapp/cms/plugins/calendar/plugin.xml	(working copy)
@@ -229,6 +229,11 @@
                         <label i18n="true">CALENDAR_SERVICE_AGENDA_ICAL_DOWNLOAD_LABEL</label>
                         <description i18n="true">CALENDAR_SERVICE_AGENDA_ICAL_DOWNLOAD_DESC</description>
                     </parameter>
+                    <parameter name="rss" type="boolean">
+                         <label i18n="true">CALENDAR_SERVICE_AGENDA_RSS_FEED</label>
+                         <description i18n="true">CALENDAR_SERVICE_AGENDA_RSS_FEED_DESC</description>
+                         <default-value>false</default-value>
+                    </parameter>
                     <parameter name="link" type="string">
                         <label i18n="true">CALENDAR_SERVICE_AGENDA_LINK_LABEL</label>
                         <description i18n="true">CALENDAR_SERVICE_AGENDA_LINK_DESC</description>
@@ -257,6 +262,13 @@
         </extensions>
     </feature>
     
+    <feature name="calendar.helper">
+    	<components>
+    		<component role="org.ametys.plugins.calendar.events.EventsFilterHelper"
+    					class="org.ametys.plugins.calendar.events.EventsFilterHelper"/>
+    	</components>
+    </feature>
+    
     <feature name="calendar.services.cache.policy">
         <extensions>
             <extension point="org.ametys.web.cache.pageelement.PageElementCachePolicyExtensionPoint/zoneItem"
Index: webapp/cms/plugins/web/src/org/ametys/web/repository/page/actions/SetFilterInRequestAttributesAction.java
===================================================================
--- webapp/cms/plugins/web/src/org/ametys/web/repository/page/actions/SetFilterInRequestAttributesAction.java	(revision 25541)
+++ webapp/cms/plugins/web/src/org/ametys/web/repository/page/actions/SetFilterInRequestAttributesAction.java	(working copy)
@@ -86,7 +86,7 @@
     private static final String __FILTERS_TRUE = "ft-true";
     private static final String __FILTERS_FALSE = "ft-false";
 
-    private AmetysObjectResolver _resolver;
+    protected AmetysObjectResolver _resolver;
     private ContentFilterExtensionPoint _filterExtPt;
     private ServiceExtensionPoint _serviceEP;
     private SiteManager _siteManager;
