- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
Class ::caldav::CalDAV
::caldav::CalDAVcreate ... \
[ -url (default "/caldav/") ]
Defined in
Class Relations
::xotcl::Class create ::caldav::CalDAV \ -superclass ::xo::ProtocolHandlerMethods (to be applied on instances)
GET (scripted, public)
<instance of caldav::CalDAV> GET
GET calendar content in ical syntax. The method can either return an individual calendar item based on a cal_uid or a full calendar (may be an aggregated calendar) depending on the syntax of the URL. - individual cal_items (must end with .ics) /caldav/calendar/12872/12907.ics - complete calendar /caldav/calendar /caldav/calendar?calendar_ids=12872 /caldav/calendar/12872 where the "12872" is a calendar_id and "12907" is the UID of a calendar item.
- Testcases:
- GET
# the following getcontent call is just for consistent logging :getcontent # # set tail [lindex ${:urlv} end] set mimetype "text/calendar" set code 404 set resp "" :debug ":urlv [list ${:urlv}]" if {[file extension $tail] eq ".ics"} { # # Retrieve a single item identified by an UID # set uid [:get_uid_from_href ${:uri}] :debug "return single calendar item for uid $uid" # # We need the calendar_ids for consistent naming of # calendars (aggregated, etc.).... # if {[string is integer -strict [lindex ${:urlv} end-1]]} { set calendar_ids [lindex ${:urlv} end-1] } else { set calendar_ids "" } :calendar_ids $calendar_ids # # GN TODO: running two queries is not optimal but having # these separate eases type mismatch. # # cal_uids(cal_uid, on_which_activity references acs_activities(activity_id), ical_vars) # cal_items(cal_item_id references acs_events(event_id), on_which_calendar, ...) # acs_events(event_id, ..., activity_id references acs_activities(activity_id), ...) # set cal_items [::xo::dc list_of_lists -prepare varchar get_calitem { select e.event_id as cal_item_id, u.ical_vars, a.creation_date, a.last_modified from cal_uids u, acs_events e, acs_objects a where u.cal_uid = :uid and e.activity_id = u.on_which_activity and a.object_id = e.event_id limit 1 }] :debug "cal_items 1 (join on <$uid>): <$cal_items>" if {[llength $cal_items] == 0 && [string is integer -strict $uid]} { set cal_items [::xo::dc list_of_lists -prepare integer get_calitem { select e.event_id as cal_item_id, '' as ical_vars, a.creation_date, a.last_modified from acs_events e, acs_objects a where e.activity_id = :uid and a.object_id = e.event_id limit 1 }] } #:debug "cal_items 2: <$cal_items>" if {[llength $cal_items] == 1} { lassign [lindex $cal_items 0] cal_item_id ical_vars creation_date last_modified calendar::item::get -cal_item_id $cal_item_id -array c :debug "calendar::item::get -cal_item_id $cal_item_id->\n[array get c]" set vevent [calitem new -uid $uid -creation_date $creation_date -last_modified $last_modified -dtstart $c(start_date_ansi) -is_day_item [dt_no_time_p -start_time $c(start_date_ansi) -end_time $c(end_date_ansi)] -formatted_recurrences [calendars format_recurrence -recurrence_id $c(recurrence_id)] -dtend $c(end_date_ansi) -ical_vars $ical_vars -location $c(location) -summary $c(name) -description $c(description)] unset -nocomplain c append resp [$vevent as_ical_calendar -calendar_name [:calendarName]] $vevent destroy # GN TODO: do we need always the database to get an etag ? ns_set put [ns_conn outputheaders] ETag [subst {"[:getETagByUID $uid]"}] set code 200 } else { # # Nothing found or a weird result (multiple items). If # nothing is found, we have the error status code from # above. On too many results, raise an exception. # if {[llength $cal_items] > 1} { error "cal_item query based on cal_uid $uid lead to multiple results [llength $cal_items]" } } } else { # # Retrieve a calendar. # # This query is run currently without a time constraints. # # GN TODO: not sure, returning always the full calendar is # a good idea, my personal calendar has more than 10 years # of data ... but maybe this is not often executed. # # The query parameter "calendar_ids" is used e.g. for # regression testing. # # GN TODO: # if {[llength ${:urlv}] > 1} { set calendar_ids [lindex ${:urlv} 1] } else { set calendar_ids [ns_queryget calendar_ids ""] } :calendar_ids $calendar_ids if {$calendar_ids ne ""} { # # For specified calendars, return the calendar name of # the first calendar_id # set calendar_query "-calendar_ids $calendar_ids" } else { set calendar_query "" } set resp [calendars header -calendar_name [:calendarName]] foreach item [calendars get_calitems -user_id ${:user_id} {*}$calendar_query] { append resp [$item as_ical_event] } append resp [calendars footer] set code 200 } :response $code $mimetype $respPROPFIND (scripted, public)
<instance of caldav::CalDAV> PROPFIND
read and answer PROPFIND requests RFC 2518, section 8.1 https://tools.ietf.org/html/rfc4918#page-35
#https://github.com/seiyanuta/busybook/blob/master/Documentation/caldav-request-examples.md # Do a PROPFIND on the url the user supplied, requesting {DAV:}current-user-principal. # Using this url, you do a PROPFIND to find out more information about the user. # Here, you should typically request for the calendar-home-set property in the caldav namespace. # Then, using the calendar-home-set, do a PROPFIND (depth: 1) to find the calendars. # A client MUST submit a Depth header with a value of "0", "1", # or "infinity" with a PROPFIND request. Servers MUST support # "0" and "1" depth requests on WebDAV-compliant resources and # SHOULD support "infinity" requests. Servers SHOULD treat a # request without a Depth header as if a "Depth: infinity" # header was included. # set depth [ns_set iget [ns_conn headers] Depth "infinity"] #ns_log notice "caldav: depth = $depth" set content [:getcontent {Depth "infinity"}] set doc [:parseRequest $content] ns_log notice "after parseRequest <$content>" if {$doc eq ""} { return [:request_error "could not parse request. Probably invalid XML"] } set root [$doc documentElement] # # Element name of root node must be "propfind" # if {[$root localName] ne "propfind"} { $doc delete return [:request_error "invalid request, no <propfind> element"] } set prop [$root firstChild] # # child of root must be prop or allprop # set elementName [$prop localName] if {$elementName ni {prop allprop}} { $doc delete return [:request_error "invalid request, no <prop> or <allprop> element, but '$elementName' provided"] } # # Special case allprop: return all properties # if {$elementName eq "allprop"} { dom parse -- { <A:prop xmlns:A="DAV:"> <A:getcontenttype/> <A:getetag/> <A:sync-token/> <A:supported-report-set/> <c:getctag xmlns:c="http://calendarserver.org/ns/"/> <A:resourcetype/> <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/> <B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/> <A:displayname/> <A:current-user-principal/> </A:prop> } propTree set prop [$propTree firstChild] } set response "" # The incoming request URI should fall into the following categories # # - TOPLEVEL: /caldav # - PRINCIPAL: /caldav/principal # - FULL CALENDAR: /caldav/calendar # # GN TODO: remove the following test when we are sure it is ok. #ns_log notice "PROPFIND <${:uri}>, tail <[file tail ${:uri}]> urlv: <${:urlv}>" if {[file tail ${:uri}] ne [lindex ${:urlv} end]} { ns_log notice "========================== <[file tail ${:uri}]> ne <[lindex ${:urlv} end]>" error } switch [lindex ${:urlv} end] { "" { ns_log notice "=== CalDAV /caldav/ depth $depth" append response [:generateResponse -queryType top -user_id ${:user_id} $prop] if {$depth ne "0"} { # # Not sure, what should be sent in such cases. we # could send "principal" or "calendar" replies. ns_log notice "CalDAV /caldav/ query <${:uri}> with depth $depth" #append response [:generateResponse -queryType principal $prop] } } "principal" { # A principal is a network resource that represents a distinct human or # computational actor that initiates access to network resources. # https://tools.ietf.org/html/rfc3744 append response [:generateResponse -queryType principal $prop] # # here we ignore depth for the time being # } "calendar" { # TODO: we assume, we are working on the aggregated calendar :calendar_ids "" #set calendar_id [::caldav::get_sync_calendar -user_id ${:user_id}] #append response [:generateResponse -queryType calendar -calendar_id $calendar_id $prop] append response [:generateResponse -queryType calendar -calendar_id "" $prop] #ns_log notice "=== CALENDAR depth $depth FIND getetag=[$prop selectNodes -namespaces {d DAV:} //d:getetag] PROP [$prop asXML] " if {$depth ne "0"} { # # Search in depth 1 to deeper. Since all our # calendar data is at depth 1, there is no need to # differentiate. # # GN TODO: In general, PROPFIND on calendars is # needed also without etags, not sure, where the # restriction concerning "etag"comes from. # if {[$prop selectNodes -namespaces {d DAV:} //d:getetag] ne ""} { # # Query attributes containing also etags for # this users' resources # foreach item [calendars get_calitems -user_id ${:user_id}] { append response [:generateResponse -queryType resource -cal_item $item $prop] } } else { ns_log notice "CalDAV: ===== calendar query on components with depth $depth on [$prop asXML]" } } } default { $doc delete ad_log warning "caldav: PROFIND called for unknown resource: '${:uri}'" :response 404 text/plain "no such resource: '${:uri}'" return #return [:request_error "invalid URI '${:uri}'"] } } # create response append resp {<?xml version="1.0" encoding="utf-8" ?>} \n {<d:multistatus } {xmlns:d="DAV:" } {xmlns:cs="http://calendarserver.org/ns/" } {xmlns:c="urn:ietf:params:xml:ns:caldav" } {xmlns:ical="http://apple.com/ns/ical/">} $response </d:multistatus> :response 207 text/xml $resp $doc deletePROPPATCH (scripted)
<instance of caldav::CalDAV> PROPPATCH
- Testcases:
- Thunderbird_subscribe, caldav, macOS_subscribe
set content [:getcontent] set doc [:parseRequest $content] # TODO: we assume, we are working on the aggregated calendar :calendar_ids "" if {$doc eq ""} { return [:request_error "request document invalid:\n$content"] } set reply "" set innerresponse "" set root [$doc documentElement] set props [$root selectNodes -namespaces ${:namespaces} /d:propertyupdate/d:set/d:prop] if {[llength $props] == 0} { ns_log Warning "PROPPATCH: invalid request: no property /d:propertyupdate/d:set/d:prop in\n$content" set statusCode 400 } else { # # Return a multistatus with all properties forbidden. # foreach n [$props childNodes] { append innerresponse [subst {<d:propstat> <d:prop><Z:[$n localName] xmlns:Z="[$n name]"/></d:prop> <d:status>HTTP/1.1 403 Forbidden</d:status> </d:propstat>}] } set resp [subst {<?xml version="1.0" encoding="utf-8" ?> <d:multistatus xmlns:d="DAV:"> <d:response> <d:href>[string trimright [ns_conn url] "/"]</d:href> $innerresponse </d:response> </d:multistatus>}] set statusCode 207 } :response $statusCode text/xml $resp $doc deletePUT (scripted, public)
<instance of caldav::CalDAV> PUT
UPDATE (SAVE?) a single calendar item denoted by a uid of a calendar item.
- Testcases:
- Thunderbird_add_event, macOS_add_location, macOS_add_event
set content [:getcontent {If-Match ""}] set ifmatch [ns_set iget [ns_conn headers] If-Match "*"] # # The request URI has to end with UID.ics, but we get the # actual UID from the ical UID field of the parsed content. # if {![string match *.ics ${:uri}]} { return [:request_error "Trying to PUT on a calendar that does not end with *.ics: ${:uri}"] } try { set sync_calendar_id [::caldav::get_sync_calendar -user_id ${:user_id}] } on error {errorMsg} { return [:request_error "no private calendar found for ${:user_id}"] } set items [calendars parse $content] :debug "caldav parser returned items <$items>" foreach item $items { set uid [$item cget -uid] :debug [$item serialize] if {$ifmatch eq "*"} { # # Create new entry # :debug "add a new entry" set calendar_id $sync_calendar_id # # We should probably check, whether we can really # write on the calendar in the general case, but here, # we know, the sync calendar is always writable for the # current user. # # - GN TODO: not sure, when mutating the UID is necessary, and # whether adding a suffix with the user_id is the best solution. # So we deactivate this code for now.... # if {0} { set user_id ${:user_id} if {[::xo::dc get_value -prepare varchar,integer uid_exists { select 1 from cal_uids u join acs_objects o on (o.object_id = u.on_which_activity) where cal_uid = :uid and o.creation_user != :user_id } 0]} { ad_log warning "uid already exists for another user, suffixing ${uid}-${:user_id}" $item set uid "${uid}-${:user_id}" } } } else { # # Update an existing entry # :debug "update existing entry..." lappend clauses {*}[calendars communityCalendarClause ${:user_id}] {*}[calendars alwaysQueriedClause ${:user_id}] set all_calendar_ids [::xo::dc list read_only_cals [subst { select calendar_id from ([join $clauses " union "]) as cals }]] set cal_infos [calendars get_calendar_and_cal_item_from_uid -calendar_ids $all_calendar_ids $uid] :debug "cal_infos for calendar_ids $all_calendar_ids uid $uid -> <$cal_infos>" if {$cal_infos ne ""} { set cal_info [lindex $cal_infos 0] lassign $cal_info calendar_id cal_item_id :debug "write check needed? [expr {$calendar_id ne $sync_calendar_id}]" if {$calendar_id != $sync_calendar_id} { set can_write_p [permission::permission_p -object_id $cal_item_id -privilege write] :debug "write check: $can_write_p (calendar_id $calendar_id, sync_calendar_id $sync_calendar_id)" if {!$can_write_p} { ns_log warning "CalDAV: user tried to perform a PUT on a read only item item: $cal_item_id user ${:user_id}" if {[string match "*CalDavSynchronizer*" ${:user_agent}]} { # # Outlook synchronizer will continue # hammering if 403 is returned, # therefore try a different status # code. # # GN TODO: please check, what happens # with status code 412 on Outlook, # since 202 probably silently swallows # the update, which never happens. # :debug "CalDav: outlook client encountered" set statusCode 202 } else { set statusCode 412 } return [:response $statusCode text/plain {}] } } } } # # save the item # :debug "updating item [$item cget -uid] in calendar $sync_calendar_id" :item_update -calendar_id $sync_calendar_id -item $item #ns_log notice "CalDAV PUT: [$item serialize]" ns_set put [ns_conn outputheaders] ETag [subst {"[:getETagByUID [$item cget -uid]]"}] $item destroy :response 201 text/plain {} return } # TODO what happens hereREPORT (scripted, public)
<instance of caldav::CalDAV> REPORT
CalDAV REPORT Method, see RFC 3253, section 3.6
- Testcases:
- REPORT_ios, Thunderbird_subscribe, Thunderbird_add_event, macOS_subscribe
set content [:getcontent] set doc [:parseRequest $content] if {$doc eq ""} { return [:request_error "empty reports are not allowed"] } # # Currently, three reports are supported # # - calendar-multiget # - calendar-query # - sync-collection # $doc documentElement root set responses_xml "" # TODO: we assume, we are working on the aggregated calendar :calendar_ids "" if {[$root selectNodes -namespaces {c urn:ietf:params:xml:ns:caldav} "//c:calendar-multiget"] ne ""} { set ics_set [:calendar-multiget [$root firstChild]] } elseif {[$root selectNodes -namespaces {c urn:ietf:params:xml:ns:caldav} "//c:calendar-query"] ne ""} { set ics_set [:calendar-query $root] } elseif {[$root selectNodes -namespaces {d DAV:} "//d:sync-collection"] ne ""} { set ics_set [:sync-collection $root responses_xml] } else { #unknown type requested, aborting $doc delete return [:request_error "request type unknown [$root localName]"] } set props [$root selectNodes -namespaces {d DAV:} "d:prop"] foreach ics $ics_set { append responses_xml [:generateResponse -queryType resource -cal_item $ics $props] } append xml {<?xml version="1.0" encoding="utf-8"?>} \n {<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns">} \n $responses_xml \n </d:multistatus> :response 207 text/xml $xml $doc deletecalcCtag (scripted, public)
<instance of caldav::CalDAV> calcCtag user_id
Generate a ctag (collection entity tag). The calendar ctag is like a resource etag; it changes when anything in the (joint) calendar has changed. This implementation returns a md5 value from the sum of the cal_item_ids, which changes when an item is deleted or a new community is joined, and the latest modification date of the cal_item_ids.
- Parameters:
- user_id (required)
- Testcases:
- macOS_subscribe
lappend clauses {*}[calendars communityCalendarClause $user_id] {*}[calendars alwaysQueriedClause $user_id] #ns_log notice "calendar_clause [join $clauses { union }]" return [::xo::dc get_value lastmod [subst { select md5(sum(cal_item_id)::text || max(last_modified)::text) from cal_items ci join acs_objects ao on (ao.object_id = ci.cal_item_id) where ci.on_which_calendar in ([join $clauses " union "]) }]]calendar-multiget (scripted, public)
<instance of caldav::CalDAV> calendar-multiget prop
calendar-multiget REPORT is used to retrieve specific calendar object resources from within a collection, if the Request- URI is a collection, or to retrieve a specific calendar object resource, if the Request-URI is a calendar object resource. This report is similar to the CALDAV:calendar-query REPORT (see Section 7.8), except that it takes a list of DAV:href elements, instead of a CALDAV:filter element, to determine which calendar object resources to return. https://icalendar.org/CalDAV-Access-RFC-4791/7-9-caldav-calendar-multiget-report.html
- Parameters:
- prop (required)
- Testcases:
- REPORT_ios
#ns_log notice "==== calendar-multiget {$prop}" set cal_uids {} set act_ids {} foreach node [$prop selectNodes -namespaces {d DAV:} //d:href/text()] { set href [$node asText] set id [:get_uid_from_href $href] if {$id eq ""} { ns_log notice "CalDAV calendar-multiget: ignore href '$href'" continue } if {[string is integer -strict $id] && [::xo::dc get_value -prepare integer is_activity_id { select 1 from acs_activities where activity_id = :id } 0] != 0 } { lappend act_ids $id } else { lappend cal_uids [ns_urldecode $id] } } # # GN TODO: it is probably better to use a UNION instead of the # construct of "coalesce()" and "OR" below. We need to # test/benchmark with a full database with real-world data for # this. # set uid_clauses {} if {[llength $cal_uids] > 0} { lappend uid_clauses "u.cal_uid in ([ns_dbquotelist $cal_uids])" } if {[llength $act_ids] > 0} { lappend uid_clauses "e.activity_id in ([ns_dbquotelist $act_ids])" } ns_log notice uid_clauses=$uid_clauses set recurrences {} set ics_set {} # # TODO: why not [calendar get_calitems] ? # foreach row [::xo::dc list_of_lists get_calitems [subst { select md5(last_modified::text) as etag, coalesce(cal_uid, e.activity_id::varchar), ical_vars, on_which_calendar, c.item_type_id, to_char(start_date, 'YYYY-MM-DD HH24:MI:SS'), to_char(end_date, 'YYYY-MM-DD HH24:MI:SS'), coalesce(e.name, a.name), coalesce(e.description, a.description), c.cal_item_id, recurrence_id, creation_date, last_modified from acs_objects ao, acs_events e left outer join cal_uids u on u.on_which_activity = e.activity_id, acs_activities a, timespans s, time_intervals t, cal_items c where e.event_id = ao.object_id and a.activity_id = e.activity_id and c.cal_item_id = e.event_id and e.timespan_id = s.timespan_id and s.interval_id = t.interval_id and ( [join $uid_clauses " or "]) order by start_date asc }]] { lassign $row etag cal_uid ical_vars calendar_id item_type start_date end_date name description cal_item_id recurrence_id creation_date last_modified if {$recurrence_id ne "" && $recurrence_id in $recurrences} { continue } # set item [::caldav::calitem new # -id $cal_item_id # -uid $cal_uid # -ical_vars $ical_vars # -etag $etag # -calendar $calendar_id # -creation_date $creation_date # -last_modified $last_modified # -dtstart $start_date # -is_day_item [dt_no_time_p -start_time $start_date -end_time $end_date] # -formatted_recurrences [calendars format_recurrence -recurrence_id $recurrence_id] # -dtend $end_date # -summary $name # -description $description # -item_type $item_type] set item [::caldav::calitem new -uid $cal_uid -ical_vars $ical_vars -etag $etag -creation_date $creation_date -last_modified $last_modified -dtstart $start_date -is_day_item [dt_no_time_p -start_time $start_date -end_time $end_date] -formatted_recurrences [calendars format_recurrence -recurrence_id $recurrence_id] -dtend $end_date -summary $name -description $description ] $item destroy_on_cleanup lappend ics_set $item lappend recurrences $recurrence_id } return $ics_setcalendar-query (scripted, public)
<instance of caldav::CalDAV> calendar-query root
Client wants the complete calendar Open: restrict to types
- Parameters:
- root (required)
- Testcases:
- REPORT_ios
set start "" set end "" # # determine filters # set time_range [$root selectNodes -namespaces {c urn:ietf:params:xml:ns:caldav} "//c:time-range"] #ns_log notice "time-range : $time_range" if {$time_range ne ""} { if {[$time_range hasAttribute start]} { set start [$time_range getAttribute start] set start [::xo::ical clock_to_oacstime [::xo::ical utc_to_clock $start]] } if {[$time_range hasAttribute end]} { set end [$time_range getAttribute end] set end [::xo::ical clock_to_oacstime [::xo::ical utc_to_clock $end]] } } ns_log notice "calendar-query: start time $start end time $end" # # components # set time_range [$root selectNodes -namespaces {c urn:ietf:params:xml:ns:caldav} "//c:comp-filter"] set fetch_vtodos 0 set fetch_vevents 0 foreach x $time_range { if {[$x getAttribute name] eq "VEVENT"} {set fetch_vevents 1} if {[$x getAttribute name] eq "VTODO"} {set fetch_vtodos 1} } set ics_set "" if {$fetch_vevents} { set ics_set [calendars get_calitems -user_id ${:user_id} -start_date $start -end_date $end] } return $ics_setgenerateResponse (scripted, public)
<instance of caldav::CalDAV> generateResponse \ -queryType queryType [ -user_id user_id ] \ [ -calendar_id calendar_id ] [ -cal_item cal_item ] node
Return a <response> ... </response> entry for the URL specified in the ${:url} and for the query attributes specified in the tdom node. The attributes user_id, calendar_id, or cal_item have to be set according to the queryType.
- Switches:
- -queryType (required)
- is an abstraction of the query url and can be "calendar", "resource", "top", or "principal"
- -user_id (optional)
- -calendar_id (optional)
- -cal_item (optional)
- Parameters:
- node (required)
- Testcases:
- PROPFIND_ios, PROPFIND_android, Thunderbird_subscribe, macOS_subscribe
# @prop: requested properties as tdom nodes # generate xml for this resource #ns_log notice "generateResponse $queryType" set :queryType $queryType switch $queryType { "resource" { set href "${:url}calendar/[ns_urlencode [$cal_item cget -uid]].ics" set resource $cal_item } "calendar" { set href "${:url}calendar/" set resource $calendar_id } "top" { set href ${:url} set resource $user_id } "principal" { set href "${:url}principal" set resource "none" } "default" { error "invalid input" } } ns_log notice "generateResponse href=$href, resource=$resource child nodes [llength [$node childNodes]]" set not_found_props "" set found_props "" foreach childNode [$node childNodes] { lassign [:returnXMLelement $resource $childNode] returncode val if {$returncode} { append not_found_props $val \n } else { append found_props $val \n } } :debug "found_props $found_props, not_found_props $not_found_props" append result <d:response> \n <d:href>$href</d:href> \n if {$found_props ne ""} { # # return 200 for properties that were found # append result <d:propstat> \n <d:prop>$found_props</d:prop> \n "<d:status>HTTP/1.1 200 OK</d:status>" \n </d:propstat> } if {$not_found_props ne ""} { # # return 404 for properties that were not found # append result \n <d:propstat> \n <d:prop>$not_found_props</d:prop> \n "<d:status>HTTP/1.1 404 Not Found</d:status>" \n </d:propstat> } append result \n </d:response> return $resultproperty=cs-getctag (scripted)
<instance of caldav::CalDAV> property=cs-getctag
- Testcases:
- PROPFIND_ios, caldav, PROPFIND_android, PROPFIND_thunderbird
# Specifies a "synchronization" token used to indicate when # the contents of a calendar or scheduling Inbox or Outbox # collection have changed. if {${:queryType} eq "calendar"} { return [:calcCtag ${:user_id}] } else { return "" }returnXMLelement (scripted, public)
<instance of caldav::CalDAV> returnXMLelement resource node
Return a pair of values indicating success and the value to be returned. While the property=* methods above return just values, this method wraps it into XML elements for returning it.
- Parameters:
- resource (required)
- node (required)
- Testcases:
- PROPFIND_ios, PROPFIND_android, Thunderbird_subscribe, macOS_subscribe
# # set property [$node localName] set ns [$node name] :debug "<$property> <$ns> resource $resource queryType <${:queryType}>" if {[info exists :xmlns($ns)]} { set outputNs [set :xmlns($ns)] set methodName property=$outputNs-$property #:debug "call method $methodName" try { set value [:$methodName $resource $node] :debug "<$property> <$ns> resource $resource queryType <${:queryType}> -> '$value'" } on error {errorMsg} { ns_log warning "CalDAV returnXMLelement: call method $methodName raised exception: $errorMsg" set value "" } if {$value eq ""} { set result [list 1 <${outputNs}:$property/>] #:debug "${:method} query $methodName returns empty (probably not found)" } else { set result [list 0 <${outputNs}:$property>$value</${outputNs}:$property>] } } else { ns_log warning "CalDAV: client requested a element with unknown namespace $ns known ([array names :xmlns])" set result [list 1 [subst {<[$node nodeName] xmlns:[$node prefix]="[$node namespaceURI]"/>}]] } return $resultsync-collection (scripted, public)
<instance of caldav::CalDAV> sync-collection root extraXMLvar
sync
- Parameters:
- root (required)
- extraXMLvar (required)
- Testcases:
- REPORT_ios
upvar $extraXMLvar extraXML set props [$root selectNodes -namespaces {d DAV:} "//d:prop"] #set sync_level_node [$root selectNodes -namespaces {d DAV:} "//d:sync-level"] set sync_token_node [$root selectNodes -namespaces {d DAV:} "//d:sync-token"] if {$sync_token_node ne ""} { set sync_token [$sync_token_node text] } else { set sync_token "" } :debug "received sync-token <$sync_token>" # # Calculate a new sync token and return this as extraXML # set new_sync_token [:calcSyncToken ${:user_id}] set extraXML <d:sync-token>$new_sync_token</d:sync-token> if {$sync_token eq ""} { # # return all cal_items # set ics_set [calendars get_calitems -user_id ${:user_id}] } elseif {$sync_token ne $new_sync_token} { # # return cal_items since last sync_token # set ics_set [calendars get_calitems -user_id ${:user_id} -start_date [ns_fmttime $sync_token "%Y-%m-%d 00:00"] -end_date [ns_fmttime $new_sync_token "%Y-%m-%d 00:00"]] } else { # # return cal_items since the day of the sync_token # set ics_set [calendars get_calitems -user_id ${:user_id} -start_date [ns_fmttime $sync_token "%Y-%m-%d 00:00"]] } return $ics_set
- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables