# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Appointments Web"""

__version__ = '$Revision$'[11:-2]

import calendar
import time

import glasnost.common.tools_new as commonTools

from glasnost.proxy.AppointmentsProxy import *
from ObjectsWeb import register, AdminMixin, ObjectWebMixin, ObjectsWebMixin

import calendaring
from tools import *

class AdminAppointments(AdminMixin, AdminAppointments):
    categoriesGroupId_kind_widget_fieldLabel = N_('Categories Group')
    categoriesGroupId_kind_widgetName = 'SelectId'
register(AdminAppointments)


_Appointment = Appointment
class Appointment(ObjectWebMixin, Appointment):

    categoriesSet = None
    class categoriesSet_kindClass:
        _kindName = 'Sequence'
        importExport = 'private'
        isRequiredInEditMode = 1
        label = N_('Categories')
        minCount = 0
        stateInEditMode = 'read-write'
        class itemKind_valueClass:
            _kindName = 'Id'
            def getValues(self, slot):
                admin = slot.getObject().getWeb().getAdmin()
                groupsProxy = getProxyForServerRole('groups')
                if not admin.categoriesGroupId:
                    return []
                group = groupsProxy.getObject(admin.categoriesGroupId)
                return group.membersSet

    end_kind_widget_fieldLabel = N_('End')

    participantsSet_kind_itemKind_value_defaultValue = 'user'
    participantsSet_kind_balloonHelp = N_(
         'Select the people and groups who are assigned to this appointment.')
    participantsSet_kind_label = N_('Participants')

    start_kind_widget_fieldLabel = N_('Start')

    title_kind_widget_fieldLabel = N_('Title')
    title_kind_widget_size = 40
    title_kind_widgetName = 'InputText'
    
    #def getOrderedLayoutSlotNames(self, parentSlot = None):
    #    slotNames = _Appointment.getOrderedLayoutSlotNames(
    #                                        self, parentSlot = parentSlot)
    #    #slotNames += [ 'categoriesSet' ]
    #    return slotNames

    def getLabelTranslated(self, destinationLanguages = None, multiCall = None,
                withDate = 0, withHour = 0):
        label = ObjectWebMixin.getLabelTranslated(self, destinationLanguages,
                multiCall)
        dateTime = self.getDateTime(withDate, withHour)
        if dateTime:
            return '%s - %s' % (dateTime, label)
        return label
    
    def getSlotNames(self, parentSlot = None):
        slotNames = _Appointment.getSlotNames(self, parentSlot = parentSlot)
        admin = self.getWeb().getAdmin()
        if not admin.categoriesGroupId:
            slotNames.remove('categoriesSet')
        return slotNames

    def toVevent(self):
        s = []
        s.append("""BEGIN:VEVENT
UID:%s
SUMMARY:%s
URL:%s
SEQUENCE:%s""" % (
                self.id,
                self.title,
                X.idUrl(self.id).getAsAbsoluteUrl(),
                self.version))
        if self.body:
            # TODO: extract text from body
            s.append("""DESCRIPTION:%s""" % utf8(self.body.replace('\n', '\\n')))
        if not self.getBeginningHourAndMinute():
            date = '%d%02d%02d' % tuple(
                    time.localtime(self.start)[:3])
            s.append("""DTSTART
 ;VALUE=DATE
 :%s
DTEND:%sT235900""" % (date, date) )
        else:
            beginning = '%d%02d%02dT%02d%02d%02d' % tuple(
                    time.localtime(self.start)[:6])
            if not self.end:
                self.end = self.start + 3600
            end = '%d%02d%02dT%02d%02d%02d' % tuple(
                    time.localtime(self.end)[:6])
            s.append("""DTSTART:%s\nDTEND:%s""" % (beginning, end))

        s.append('END:VEVENT\n')
        return '\n'.join(s)
register(Appointment)


def weekNumberToDate(year, weekNumber):
    jan4thSecs = time.mktime((year, 1, 4, 0, 0, 0, 0, 0, 0))
    secondsSinceJan4th = jan4thSecs + (weekNumber-1)*7*24*60*60
    weekTuple = time.localtime(secondsSinceJan4th)
    daysToAdd = - weekTuple[6]
    secondsSinceJan4th += daysToAdd*24*60*60
    weekTuple = time.localtime(secondsSinceJan4th)
    return weekTuple


def dateToWeekNumber(year, month, day):
    janFirstSecs = time.mktime((year, 1, 1, 0, 0, 0, 0, 0, 0))
    janFirstTuple = time.localtime(janFirstSecs)
    if janFirstTuple[6] < 4:
        week0Secs = janFirstSecs - janFirstTuple[6]*24*60*60
    else:
        week0Secs = janFirstSecs + (7-janFirstTuple[6])*24*60*60
        
    secondsSinceEpoch = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
    diffSecs = secondsSinceEpoch - week0Secs
    weekNumber, mod = divmod(diffSecs, 7*24*60*60)
    weekNumber = int(weekNumber) + 1
    if weekNumber == 53:
        year, weekNumber = year+1, 1
    if weekNumber == 0:
        year, weekNumber = year-1, 52
    return (year, weekNumber)


class AppointmentsWeb(ObjectsWebMixin, AppointmentsProxy):

    def agenda(self):
        now = time.time()
        objects = self.getObjects().values()

        pastObjects = [x for x in objects if x.start < now]
        pastObjects.sort(lambda x,y: -cmp(x.start, y.start))

        futureObjects = [x for x in objects if x.start > now]
        futureObjects.sort(lambda x,y: cmp(x.start, y.start))

        layout = X.array()

        if futureObjects:
            table = X.table(_class = 'objects-table')
            table += X.tr(
                    X.th(_('Date')),
                    X.th(_('Label')))
            for object in futureObjects:
                table += X.tr(
                        X.td(object.getDateTime(withDate = 1, withHour = 1)),
                        X.td(X.objectHypertextLabel(object.id)))
            layout += X.h2(_('Future events'))
            layout += table

        if pastObjects:
            table = X.table(_class = 'objects-table')
            table += X.tr(
                    X.th(_('Date')),
                    X.th(_('Label')))
            for object in pastObjects:
                table += X.tr(
                        X.td(object.getDateTime(withDate = 1, withHour = 1)),
                        X.td(X.objectHypertextLabel(object.id)))
            layout += X.h2(_('Past events'))
            layout += table

        return writePageLayout(layout, _('Agenda'))
    agenda.isPublicForWeb = 1

    def day(self, year, month, day):
        canAdd = self.canAddObject()
        thisDay = time.mktime( [int(year), int(month), int(day)] + [0]*5 +
                [time.localtime()[-1]] )
        appointments = self.getAppointments(thisDay, thisDay+86400).values()
        dayAppointments = [x for x in appointments if x.start == thisDay]

        layout = X.array()

        if len(dayAppointments):
            layout += X.h2(_('Day long appointments'))
            ul = X.ul()
            for d in dayAppointments:
                ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
            layout += ul

        table = X.table(_class = 'calendar-day-full')
        layout += table
        
        caption = X.caption()
        yesterday = time.localtime(thisDay-86400)
        tomorrow = time.localtime(thisDay+86400)
        caption += X.a(href = X.actionUrl('day/%d/%d/%d' % (
                yesterday[0], yesterday[1], yesterday[2])),
                       _class = 'previous-day')('<<')
        caption += X.span()(self.formatDate(time.localtime(thisDay)))
        caption += X.a(href = X.actionUrl('day/%d/%d/%d' % (
                tomorrow[0], tomorrow[1], tomorrow[2])),
                        _class = 'next-day')('>>')
        table += caption

        skipTo = 0
        for hour in range(8, 21): # TODO: "opening hours" should be an option
            tr = X.tr()
            table += tr
            tr += X.th(scope = 'row')('%02d:00' % hour)
            if hour < skipTo:
                continue
            thisDate = list(time.localtime(thisDay))
            thisDate[3] = hour
            nextDate = thisDate[:]
            nextDate[3] += 1
            maxEnd = time.mktime(nextDate)
            while 1:
                hourAppointments = [x for x in appointments if \
                            x.start >= time.mktime(thisDate) and \
                            x.start < time.mktime(nextDate)]
                maxEnd = max([maxEnd] + [x.end for x in hourAppointments])
                if time.mktime(nextDate) < maxEnd:
                    nextDate = time.localtime(maxEnd)
                else:
                    break
            nbHour = int( math.ceil((
                    time.mktime(nextDate) - time.mktime(thisDate)) / 3600))

            hourContent = X.array()
            ul = X.ul()
            hourAppointments.sort(lambda x,y: cmp(x.start, y.start))
            for d in hourAppointments:
                ul += X.li(X.a(href = X.idUrl(d.id))(d.getLabelTranslated()))
            hourContent += ul
            className = ''
            if len(hourAppointments):
                className = 'hour-busy'
                td = X.td(_class = className)(hourContent)
            else:
                td = X.td()
            if nbHour > 1:
                td.setAttribute('rowspan', nbHour)
            tr += td
            skipTo = hour + nbHour - 1

        layout += self.getLinksLayout(year = int(year), month = int(month),
                                      day = int(day), exclude = ['day'])

        return writePageLayout(layout, self.formatDate(time.localtime(thisDay)))
    day.isPublicForWeb = 1

    def formatDate(self, dayTuple):
        # TODO: grab user or site preference for date format (euro/us/...)
        year, month, day = dayTuple[:3]
        monthLabel = calendaring.getMonthLabelNoCapitalization(month)
        return '%d %s %d' % (day, monthLabel, year)

    def getObject_handleResult(self, lazyObject):
        object = AppointmentsProxy.getObject_handleResult(self, lazyObject)
        groupsProxy = getProxyForServerRole('groups')
        object.categoriesSet = groupsProxy.getObjectIdsWithContent(object.id)
        return object


    def week(self, year, weekNumber):
        webTools.addContextualHeader('calendar.css')
        canAdd = self.canAddObject()

        today = list(time.localtime(time.time())[:3])

        year, weekNumber = int(year), int(weekNumber)

        prevYear, prevWeek = year, weekNumber-1
        if prevWeek == 0:
            prevYear, prevWeek = prevYear-1, 52
        nextYear, nextWeek = year, weekNumber+1
        if nextWeek > 52:
            nextYear, nextWeek = nextYear+1, 1

        weekTuple = weekNumberToDate(year, weekNumber)
        appointments = self.getAppointments(
                        time.mktime(weekTuple),
                        time.mktime(weekTuple)+7*24*60*60 ).values()

        table = X.table(_class = 'calendar-week-full')

        caption = X.caption()
        caption += X.a(href = X.actionUrl('week/%d/%d' % (
                prevYear, prevWeek)), _class = 'previous-week')('<<')
        caption += X.span()(_('Week %d of %d') % (weekNumber, year))
        caption += X.a(href = X.actionUrl('week/%d/%d' % (
                nextYear, nextWeek)), _class = 'next-week')('>>')
        table += caption

        table += X.colgroup()
        table += X.colgroup(_class = 'weekdays', span = 5)
        table += X.colgroup(_class = 'weekend', span = 2)
        
        weekTime = time.mktime(weekTuple)

        tr = X.tr()
        tr += X.th()
        for i in range(7):
            thisDate = time.localtime(weekTime + i*24*60*60)
            wd = _(calendaring.dayLabels[i])
            dayLabel = '%s %d %s' % (wd, thisDate[2],
                    calendaring.getMonthLabelNoCapitalization(thisDate[1]))
            tr += X.th(scope = 'col')(dayLabel)
        table += X.thead(tr)

        tbody = X.tbody()
        table += tbody
        daysFilled = [0] * 7
        tr = X.tr()
        tr += X.th(scope = 'row')
        allDay = 0
        for i in range(7):
            thisDate = list(time.localtime(weekTime + i*24*60*60))
            thisDateTime = time.mktime(thisDate)
            dayAppointments = [x for x in appointments \
                               if x.start == thisDateTime]
            if len(dayAppointments) == 0:
                if i in (5, 6):
                    tr += X.td(_class = 'weekend')
                else:
                    tr += X.td()
                continue
            allDay = 1
            content = X.array()
            ul = X.ul()
            for d in dayAppointments:
                ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
            content += ul
            className = 'hour-busy'
            if i in (5, 6):
                className += ' weekend'
            tr += X.td(_class = className)(content)
        if allDay:
            tbody += tr

        for hour in range(8, 21):
            # TODO: "opening hours" should be an option
            # TODO: should also show (where) appointments that are not in the
            #       opening hours
            tr = X.tr()
            tbody += tr
            tr += X.th(scope = 'row')('%02d:00' % hour)
            for i in range(7):
                if daysFilled[i]:
                    daysFilled[i] -= 1
                    continue
                thisDate = list(time.localtime(weekTime + i*24*60*60))
                thisDate[3] = hour
                nextDate = thisDate[:]
                nextDate[3] += 1
                maxEnd = time.mktime(nextDate)
                previousDuration = 1
                while 1:
                    hourAppointments = [x for x in appointments if \
                                x.start >= time.mktime(thisDate) and \
                                x.start < time.mktime(nextDate)]
                    maxEnd = max([maxEnd] + [x.end for x in hourAppointments])
                    if time.mktime(nextDate) < maxEnd:
                        nextDate = time.localtime(maxEnd)
                    else:
                        break
                if len(hourAppointments) == 0:
                    if i in (5, 6):
                        tr += X.td(_class = 'weekend')
                    else:
                        tr += X.td()
                    continue
                nbHour = int( math.ceil(
                        (time.mktime(nextDate) - time.mktime(thisDate)) / 3600))
                daysFilled[i] = nbHour-1

                hourContent = X.array()
                ul = X.ul()
                for d in hourAppointments:
                    ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
                hourContent += ul
                className = 'hour-busy'
                if i in (5, 6):
                    className += ' weekend'
                td = X.td(_class = className)(hourContent)
                if nbHour > 1:
                    td.setAttribute('rowspan', nbHour)
                tr += td
        
        layout = X.array(table)
        month = time.localtime(weekTime)[1]
        layout += self.getLinksLayout(year = year, month = month,
                                         exclude = ['week'])
        dayStart = self.formatDate(weekTuple)
        dayEnd = self.formatDate(time.localtime( \
                                 time.mktime(weekTuple)+6*24*60*60))
        return writePageLayout(layout, '%s - %s' % (dayStart, dayEnd))
    week.isPublicForWeb = 1

    def getSmallMonthLayout(self, year = 0, month = 0):
        today = list(time.localtime(time.time())[:3])
        if not year or not month:
            year, month = today[:2]

        year, month = int(year), int(month)
        table = X.table(_class = 'calendar-month-small')

        prevYear, prevMonth = year, month-1
        if prevMonth == 0:
            prevMonth, prevYear = 12, prevYear - 1
        nextYear, nextMonth = year, month+1
        if nextMonth == 13:
            nextMonth, nextYear = 1, nextYear + 1

        appointments = self.getAppointments(
                time.mktime((year, month, 1, 0, 0, 0, 0, 0, 0)),
                time.mktime((nextYear, nextMonth, 1, 0, 0, 0, 0, 0, 0)) ).values()

        caption = X.caption()
        caption += X.a(href = X.roleUrl(self.serverRole, 'month/%d/%d' % (
                year, month)))(
                '%s %s' % (calendaring.getMonthLabel(month), year))
        table += caption

        tr = X.tr()
        for wd in calendaring.dayLabels:
            tr += X.th(scope = 'col')(_(wd)[0])
        table += X.thead(tr)
        
        cal = calendar.monthcalendar(year, month)
        
        tbody = X.tbody()
        table += tbody
        for i in range(len(cal)):
            tr = X.tr()
            tbody += tr

            for j in range(len(cal[i])):
                d = cal[i][j]
                if d == 0:
                    dayLabel = ''
                    tr += X.td(_class = 'noday')
                    continue
                else:
                    dayLabel = str(d)
                thisDate = [year, month, d]
                nextDate = [year, month, d+1]
                timeStart = time.mktime(thisDate + [0]*5 +
                        [time.localtime()[-1]])
                timeEnd = time.mktime(nextDate + [0]*5 +
                        [time.localtime()[-1]])
                dayAppointments = [x for x in appointments if \
                            x.start >= timeStart and x.start < timeEnd]
                className = ''
                if thisDate == today:
                    className += 'today'
                elif thisDate < today:
                    className += 'past'
                else:
                    className += 'future'
                if len(dayAppointments) == 0:
                    dayContent = X.asIs(dayLabel)
                elif len(dayAppointments) == 1:
                    className += ' busy'
                    app = dayAppointments[0]
                    dayContent = X.a(href = X.idUrl(app.id),
                            title = app.getLabelTranslated(withHour = 1))(
                            dayLabel)
                else:
                    className += ' busy'
                    title = ', '.join([x.getLabelTranslated() for x in dayAppointments])
                    dayContent = X.a(
                        href = '/%s/day/%d/%d/%d' % (
                                self.serverRole, year, month, d),
                        title = title)(dayLabel)

                className += ' d%d' % (j+1)
                if j in (5, 6):
                    className += ' weekend'
                tr += X.td(_class = className)(dayContent)
        
        layout = X.array(table)

        return layout


    def month(self, year, month):
        webTools.addContextualHeader('calendar.css')
        canAdd = self.canAddObject()

        today = list(time.localtime(time.time())[:3])

        year, month = int(year), int(month)
        table = X.table(_class = 'calendar-month-full')

        prevYear, prevMonth = year, month-1
        if prevMonth == 0:
            prevMonth, prevYear = 12, prevYear - 1
        nextYear, nextMonth = year, month+1
        if nextMonth == 13:
            nextMonth, nextYear = 1, nextYear + 1

        appointments = self.getAppointments(
                time.mktime((year, month, 1, 0, 0, 0, 0, 0, 0)),
                time.mktime((nextYear, nextMonth, 1, 0, 0, 0, 0, 0, 0)) ).values()

        caption = X.caption()
        caption += X.a(href = X.actionUrl('month/%d/%d' % (
                prevYear, prevMonth)), _class = 'previous-month')('<<')
        caption += X.span()('%s %s' % (calendaring.getMonthLabel(month), year))
        caption += X.a(href = X.actionUrl('month/%d/%d' % (
                nextYear, nextMonth)), _class = 'next-month')('>>')
        table += caption

        tr = X.tr()
        tr += X.th()
        for wd in calendaring.dayLabels:
            tr += X.th(scope = 'col')(_(wd))
        table += X.thead(tr)
        
        cal = calendar.monthcalendar(year, month)
        weekNumber = dateToWeekNumber(year, month, 1) [1]
        
        tbody = X.tbody()
        table += tbody
        for i in range(len(cal)):
            tr = X.tr()
            tbody += tr

            weekLabel = X.a(href = X.actionUrl('week/%d/%d' % (
                    year, weekNumber)))(weekNumber)
            tr += X.th(scope = 'row')(weekLabel)
            weekNumber += 1
            if weekNumber == 53:
                weekNumber = 1

            for j in range(len(cal[i])):
                d = cal[i][j]
                if d == 0:
                    dayLabel = ''
                    tr += X.td(_class = 'noday')
                    continue
                else:
                    dayLabel = str(d)
                thisDate = [year, month, d]
                nextDate = [year, month, d+1]
                if canAdd:
                    dayLabel = X.array(
                            X.asIs(dayLabel),
                            X.a(href = X.actionUrl('edit').add(
                                    'start',
                                    '%d-%02d-%02d' % (year, month, d)).add(
                                    'nextUri', cleanUpUnparsedUri([])))('+'))
                dayContent = X.array()
                dayContent += X.span(_class = 'day-title')(dayLabel)

                thisDateTime = time.mktime(thisDate + [0]*5 +
                        [time.localtime()[-1]] )
                nextDateTime = time.mktime(nextDate + [0]*5 +
                        [time.localtime()[-1]] )
                dayAppointments = [x for x in appointments if \
                            (x.start >= thisDateTime and
                             x.start <  nextDateTime )
                        or ( x.end and
                             x.start < thisDateTime and
                             x.end  >= nextDateTime ) ]

                if len(dayAppointments):
                    ul = X.ul()
                    for d in dayAppointments:
                        hasDetails = 0
                        li = X.li()
                        disp, role, id = commonTools.splitId(d.id)
                        idAttribute = 'appointment-%s-%s' % (disp, id)
                        li += X.a(href = X.idUrl(d.id), id = idAttribute)(
                                d.getLabelTranslated(withHour = 1))
                        details = X.ul(_class = 'appointment-details tooltip',
                                       id = 'for-' + idAttribute)
                        start = d.getBeginningHourAndMinute()
                        end = d.getEndHourAndMinute()
                        if start and end:
                            details += X.li(_('Hour: %s - %s') % (start, end))
                            hasDetails = 1
                        if d.body:
                            details += X.li(d.body)
                            hasDetails = 1
                        if hasDetails:
                            li += details
                        ul += li
                    dayContent += ul
                className = ''
                if thisDate == today:
                    className = 'today'
                elif thisDate < today:
                    className = 'past'
                else:
                    className = 'future'
                className += ' d%d' % (j+1)
                if j in (5, 6):
                    className += ' weekend'
                tr += X.td(_class = className)(dayContent)
        
        layout = X.array(table)
        layout += self.getLinksLayout(year = year, month = month,
                                         exclude = ['month'])

        return writePageLayout(layout,
                '%s %s' % (calendaring.getMonthLabel(month), year))
    month.isPublicForWeb = 1

    def getLinksLayout(self, year = None, month = None, day = None, exclude = None):
        todayYear, todayMonth, todayDay = list(time.localtime(time.time())[:3])
        thisYear = year or todayYear
        thisMonth = month or todayMonth
        thisDay = day or todayDay
        thisWeek = dateToWeekNumber(thisYear, thisMonth, thisDay) [1]
        dayLink = X.a(href = X.actionUrl('day/%d/%d/%d' % ( 
                thisYear, thisMonth, thisDay))) (_('Day'))
        weekLink = X.a(href = X.actionUrl('week/%d/%d' % ( 
                thisYear, thisWeek))) (_('Week'))
        monthLink = X.a(href = X.actionUrl('month/%d/%d' % ( 
                thisYear, thisMonth))) (_('Month'))
        yearLink = X.a(href = X.actionUrl('year/%d' % thisYear)) (_('Year'))
        ul = X.ul(_class = 'calendar-links')
        if exclude is None:
            exclude = []
        if not 'agenda' in exclude:
            ul += X.li( X.a(href = X.actionUrl('agenda')) (_('Agenda')))
        if not 'day' in exclude:
            ul += X.li(dayLink)
        if not 'week' in exclude:
            ul += X.li(weekLink)
        if not 'month' in exclude:
            ul += X.li(monthLink)
        if not 'year' in exclude:
            ul += X.li(yearLink)
        return X.array(ul, self.getViewAllButtonsBarLayout())

    def submitAddObject(self, object):
        result = ObjectsWebMixin.submitAddObject(self, object)
        if result:
            return result
        groupsProxy = getProxyForServerRole('groups')
        for groupId in object.categoriesSet or []:
            groupsProxy.addObjectMember(groupId, object.id)
            # what happens if it fails ?

    def submitModifyObject(self, object):
        result = ObjectsWebMixin.submitModifyObject(self, object)
        if result:
            return result
        groupsProxy = getProxyForServerRole('groups')
        pastGroups = groupsProxy.getObjectIdsWithContent(object.id) or []
        nowGroups = object.categoriesSet or []
        done = []
        for groupId in pastGroups + nowGroups:
            if groupId in done:
                continue
            done.append(groupId)
            if groupId not in pastGroups:
                # adding
                groupsProxy.addObjectMember(groupId, object.id)
                continue
            if groupId not in nowGroups:
                # removing
                groupsProxy.deleteObjectMember(groupId, object.id)
                continue

    def vcal(self, id = ''):
        method = context.getVar('httpMethod')
        req = context.getVar('req')
        if id:
            if not self.hasObject(id):
                return pageNotFound()
            if not self.canGetObject(id):
                return accessForbidden()
            if method == 'DELETE':
                try:
                    self.deleteObject(id)
                except faults.Fault:
                    return HTTP_FORBIDDEN
                return HTTP_NO_CONTENT
            elif method == 'PUT':
                vCal = req.read()
                # TODO: sending vCalendar to
                #   appointmentsServer.modifyObjectWithvCal(id, vCal)
                return HTTP_NOT_IMPLEMENTED
            objects = [self.getObject(id)]
        else:
            if method == 'DELETE':
                return HTTP_FORBIDDEN
            if method == 'PUT':
                vCal = req.read()
                self.updateFromVCalendar(vCal)
                return HTTP_NO_CONTENT
            objects = self.getObjects().values()
        req.content_type = 'text/calendar; charset=iso-8859-15'
        req.send_http_header()
        if req.method == 'HEAD':
            return OK
        if req.caching:
            req.openCachePage()

        for object in objects:
            req.write("""BEGIN:VCALENDAR
VERSION:2.0
PROPID:-//glasnost.entrouvert.org/NONSGML Glasnost Calendar//EN
METHOD:PUBLISH
""")
            req.write(object.toVevent())
            req.write("""END:VCALENDAR\n""")

        if req.caching:
            req.closeCachePage()
        return OK
    vcal.isPublicForWeb = ('GET', 'DELETE', 'PUT')

    def viewAll(self):
        year, month = time.localtime(time.time())[:2]
        return self.month(year, month)
    viewAll.isPublicForWeb = 1

    def year(self, year):
        try:
            year = int(year)
        except:
            return pageNotFound()
        ul = X.ul()
        for i in range(12):
            ul += X.li( X.a(href = X.actionUrl('month/%d/%d' % (year, i+1))) (
                    '%s %s' % (calendaring.getMonthLabel(i+1), year)))
        layout = X.array()
        layout += ul
        layout += self.getLinksLayout(year = year, exclude = ['year'])

        return writePageLayout(layout, '%s' % year)
    year.isPublicForWeb = 1

