Вход Регистрация
Файл: ips_kernel/sabre/Sabre/CalDAV/Plugin.php
Строк: 723
<?php

/**
 * CalDAV plugin
 *
 * This plugin provides functionality added by CalDAV (RFC 4791)
 * It implements new reports, and the MKCALENDAR method.
 *
 * @package Sabre
 * @subpackage CalDAV
 * @copyright Copyright (C) 2007-2010 Rooftop Solutions. All rights reserved.
 * @author Evert Pot (http://www.rooftopsolutions.nl/) 
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
 */
class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {

    
/**
     * This is the official CalDAV namespace
     */
    
const NS_CALDAV 'urn:ietf:params:xml:ns:caldav';
    
    
/**
     * This is the namespace for the proprietary calendarserver extensions
     */
    
const NS_CALENDARSERVER 'http://calendarserver.org/ns/';

    
/**
     * The following constants are used to differentiate
     * the various filters for the calendar-query report
     */
    
const FILTER_COMPFILTER   1;
    const 
FILTER_TIMERANGE    3;
    const 
FILTER_PROPFILTER   4;
    const 
FILTER_PARAMFILTER  5;
    const 
FILTER_TEXTMATCH    6;

    
/**
     * The hardcoded root for calendar objects. It is unfortunate
     * that we're stuck with it, but it will have to do for now
     */
    
const CALENDAR_ROOT 'calendars';

    
/**
     * Reference to server object 
     * 
     * @var Sabre_DAV_Server 
     */
    
private $server;

    
/**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are 
     * available for the specified uri.
     *
     * @param string $uri
     * @return array 
     */
    
public function getHTTPMethods($uri) {

        
// The MKCALENDAR is only available on unmapped uri's, whose
        // parents extend IExtendedCollection
        
list($parent$name) = Sabre_DAV_URLUtil::splitPath($uri);

        
$node $this->server->tree->getNodeForPath($parent);

        if (
$node instanceof Sabre_DAV_IExtendedCollection) {
            try {
                
$node->getChild($name);
            } catch (
Sabre_DAV_Exception_FileNotFound $e) {
                return array(
'MKCALENDAR');
            }
        }
        return array();

    }

    
/**
     * Returns a list of features for the DAV: HTTP header. 
     * 
     * @return array 
     */
    
public function getFeatures() {

        return array(
'calendar-access');

    }

    
/**
     * Initializes the plugin 
     * 
     * @param Sabre_DAV_Server $server 
     * @return void
     */
    
public function initialize(Sabre_DAV_Server $server) {

        
$this->server $server;
        
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
        
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
        
$server->subscribeEvent('report',array($this,'report'));
        
$server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'));

        
$server->xmlNamespaces[self::NS_CALDAV] = 'cal';
        
$server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs';

        
$server->propertyMap['{' self::NS_CALDAV '}supported-calendar-component-set'] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet';

        
array_push($server->protectedProperties,

            
'{' self::NS_CALDAV '}supported-calendar-component-set',
            
'{' self::NS_CALDAV '}supported-calendar-data',
            
'{' self::NS_CALDAV '}max-resource-size',
            
'{' self::NS_CALDAV '}min-date-time',
            
'{' self::NS_CALDAV '}max-date-time',
            
'{' self::NS_CALDAV '}max-instances',
            
'{' self::NS_CALDAV '}max-attendees-per-instance',
            
'{' self::NS_CALDAV '}calendar-home-set',
            
'{' self::NS_CALDAV '}supported-collation-set',

            
// scheduling extension
            
'{' self::NS_CALDAV '}calendar-user-address-set'

        
);
    }

    
/**
     * This function handles support for the MKCALENDAR method
     * 
     * @param string $method 
     * @return bool 
     */
    
public function unknownMethod($method$uri) {

        if (
$method!=='MKCALENDAR') return;

        
$this->httpMkCalendar($uri);
        
// false is returned to stop the unknownMethod event
        
return false;

    }

    
/**
     * This functions handles REPORT requests specific to CalDAV 
     * 
     * @param string $reportName 
     * @param DOMNode $dom 
     * @return bool 
     */
    
public function report($reportName,$dom) {

        switch(
$reportName) { 
            case 
'{'.self::NS_CALDAV.'}calendar-multiget' :
                
$this->calendarMultiGetReport($dom);
                return 
false;
            case 
'{'.self::NS_CALDAV.'}calendar-query' :
                
$this->calendarQueryReport($dom);
                return 
false;
            default :
                return 
true;

        }


    }

    
/**
     * This function handles the MKCALENDAR HTTP method, which creates
     * a new calendar.
     * 
     * @param string $uri
     * @return void 
     */
    
public function httpMkCalendar($uri) {

        
// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
        // for clients matching iCal in the user agent
        //$ua = $this->server->httpRequest->getHeader('User-Agent');
        //if (strpos($ua,'iCal/')!==false) {
        //    throw new Sabre_DAV_Exception_Forbidden('iCal has major bugs in it's RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
        //}

        
$body $this->server->httpRequest->getBody(true);
        
$dom Sabre_DAV_XMLUtil::loadDOMDocument($body);

        
$properties = array();
        foreach(
$dom->firstChild->childNodes as $child) {

            if (
Sabre_DAV_XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue;
            foreach(
Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) {
                
$properties[$k] = $prop;
            }
        
        }

        
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');

        
$this->server->createCollection($uri,$resourceType,$properties);

        
$this->server->httpResponse->sendStatus(201);
        
$this->server->httpResponse->setHeader('Content-Length',0);
    }

    
/**
     * afterGetProperties
     *
     * This method handler is invoked after properties for a specific resource
     * are received. This allows us to add any properties that might have been
     * missing.
     * 
     * @param string $path
     * @param array $properties 
     * @return void
     */
    
public function afterGetProperties($path, &$properties) {

        
// Find out if we are currently looking at a principal resource
        
$currentNode $this->server->tree->getNodeForPath($path);
        if (
$currentNode instanceof Sabre_DAV_Auth_Principal) {

            
// calendar-home-set property
            
$calHome '{' self::NS_CALDAV '}calendar-home-set';
            if (
array_key_exists($calHome,$properties[404])) {
                
$principalId $currentNode->getName(); 
                
$calendarHomePath self::CALENDAR_ROOT '/' $principalId '/';
                unset(
$properties[404][$calHome]);
                
$properties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath);
            }

            
// calendar-user-address-set property
            
$calProp '{' self::NS_CALDAV '}calendar-user-address-set';
            if (
array_key_exists($calProp,$properties[404])) {

                
// Do we have an email address?
                
$props $currentNode->getProperties(array('{http://sabredav.org/ns}email-address'));
                if (isset(
$props['{http://sabredav.org/ns}email-address'])) {
                    
$email $props['{http://sabredav.org/ns}email-address'];
                } else {
                    
// We're going to make up an emailaddress
                    
$email $currentNode->getName() . '.sabredav@' $this->server->httpRequest->getHeader('host');
                }
                
$properties[200][$calProp] = new Sabre_DAV_Property_Href('mailto:' $emailfalse);
                unset(
$properties[404][$calProp]);

            }


        }

        if (
$currentNode instanceof Sabre_CalDAV_Calendar || $currentNode instanceof Sabre_CalDAV_CalendarObject) {
            if (
array_key_exists('{DAV:}supported-report-set'$properties[200])) {
                
$properties[200]['{DAV:}supported-report-set']->addReport(array(
                     
'{' self::NS_CALDAV '}calendar-multiget',
                     
'{' self::NS_CALDAV '}calendar-query',
                
//     '{' . self::NS_CALDAV . '}supported-collation-set',
                //     '{' . self::NS_CALDAV . '}free-busy-query',
                
));
            }
        }

        
    }

    
/**
     * This function handles the calendar-multiget REPORT.
     *
     * This report is used by the client to fetch the content of a series
     * of urls. Effectively avoiding a lot of redundant requests.
     * 
     * @param DOMNode $dom 
     * @return void
     */
    
public function calendarMultiGetReport($dom) {

        
$properties array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));

        
$hrefElems $dom->getElementsByTagNameNS('urn:DAV','href');
        foreach(
$hrefElems as $elem) {
            
$uri $this->server->calculateUri($elem->nodeValue);
            list(
$objProps) = $this->server->getPropertiesForPath($uri,$properties);
            
$propertyList[]=$objProps;

        }

        
$this->server->httpResponse->sendStatus(207);
        
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
        
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));

    }

    
/**
     * This function handles the calendar-query REPORT
     *
     * This report is used by clients to request calendar objects based on
     * complex conditions.
     * 
     * @param DOMNode $dom 
     * @return void
     */
    
public function calendarQueryReport($dom) {

        
$requestedProperties array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));

        
$filterNode $dom->getElementsByTagNameNS('urn:ietf:params:xml:ns:caldav','filter');
        if (
$filterNode->length!==1) {
            throw new 
Sabre_DAV_Exception_BadRequest('The calendar-query report must have a filter element');
        }
        
$filters Sabre_CalDAV_XMLUtil::parseCalendarQueryFilters($filterNode->item(0));

        
$requestedCalendarData true;

        if (!
in_array('{urn:ietf:params:xml:ns:caldav}calendar-data'$requestedProperties)) {
            
// We always retrieve calendar-data, as we need it for filtering.
            
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';

            
// If calendar-data wasn't explicitly requested, we need to remove 
            // it after processing.
            
$requestedCalendarData false;
        }

        
// These are the list of nodes that potentially match the requirement
        
$candidateNodes $this->server->getPropertiesForPath($this->server->getRequestUri(),$requestedProperties,$this->server->getHTTPDepth(0));

        
$verifiedNodes = array();

        foreach(
$candidateNodes as $node) {

            
// If the node didn't have a calendar-data property, it must not be a calendar object
            
if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) continue;

            if (
$this->validateFilters($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'],$filters)) {
                
                if (!
$requestedCalendarData) {
                    unset(
$node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                }
                
$verifiedNodes[] = $node;
            } 

        }

        
$this->server->httpResponse->sendStatus(207);
        
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
        
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($verifiedNodes));

    }


    
/**
     * Verify if a list of filters applies to the calendar data object 
     *
     * The calendarData object must be a valid iCalendar blob. The list of 
     * filters must be formatted as parsed by Sabre_CalDAV_Plugin::parseCalendarQueryFilters
     *
     * @param string $calendarData 
     * @param array $filters 
     * @return bool 
     */
    
public function validateFilters($calendarData,$filters) {

        
// We are converting the calendar object to an XML structure
        // This makes it far easier to parse
        
$xCalendarData Sabre_CalDAV_ICalendarUtil::toXCal($calendarData);
        
$xml simplexml_load_string($xCalendarData);
        
$xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:xcal');

        foreach(
$filters as $xpath=>$filter) {

            
// if-not-defined comes first
            
if (isset($filter['is-not-defined'])) {
                if (!
$xml->xpath($xpath))
                    continue;
                else
                    return 
false;
                
            }

            
$elem $xml->xpath($xpath);
            
            if (!
$elem) return false;
            
$elem $elem[0];

            if (isset(
$filter['time-range'])) {

                switch(
$elem->getName()) {
                    case 
'vevent' :
                        
$result $this->validateTimeRangeFilterForEvent($xml,$xpath,$filter);
                        if (
$result===false) return false;
                        break;
                    case 
'vtodo' :
                        
$result $this->validateTimeRangeFilterForTodo($xml,$xpath,$filter);
                        if (
$result===false) return false;
                        break;
                    case 
'vjournal' :
                        
// TODO: not implemented
                        
break;
                        
$result $this->validateTimeRangeFilterForJournal($xml,$xpath,$filter);
                        if (
$result===false) return false;
                        break;
                    case 
'vfreebusy' :
                        
// TODO: not implemented
                        
break;
                        
$result $this->validateTimeRangeFilterForFreeBusy($xml,$xpath,$filter);
                        if (
$result===false) return false;
                        break;
                    case 
'valarm' :
                        
// TODO: not implemented
                        
break;
                        
$result $this->validateTimeRangeFilterForAlarm($xml,$xpath,$filter);
                        if (
$result===false) return false;
                        break;

                }

            } 

            if (isset(
$filter['text-match'])) {
                
$currentString = (string)$elem;

                
$isMatching $this->substringMatch($currentString$filter['text-match']['value'], $filter['text-match']['collation']);
                if (
$filter['text-match']['negate-condition'] && $isMatching) return false;
                if (!
$filter['text-match']['negate-condition'] && !$isMatching) return false;
                
            }

        }
        return 
true;
        
    }

    private function 
validateTimeRangeFilterForEvent(SimpleXMLElement $xml,$currentXPath,array $currentFilter) {

        
// Grabbing the DTSTART property
        
$xdtstart $xml->xpath($currentXPath.'/c:dtstart');
        if (!
count($xdtstart)) {
            throw new 
Sabre_DAV_Exception_BadRequest('DTSTART property missing from calendar object');
        }

        
// The dtstart can be both a date, or datetime property
        
if ((string)$xdtstart[0]['value']==='DATE') {
            
$isDateTime false;
        } else {
            
$isDateTime true;
        }

        
// Determining the timezone
        
if ($tzid = (string)$xdtstart[0]['tzid']) {
            
$tz = new DateTimeZone($tzid);
        } else {
            
$tz null;
        }
        if (
$isDateTime) {
            
$dtstart Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtstart[0],$tz);
        } else {
            
$dtstart Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtstart[0]);
        }


        
// Grabbing the DTEND property
        
$xdtend $xml->xpath($currentXPath.'/c:dtend');
        
$dtend null;

        if (
count($xdtend)) {
            
// Determining the timezone
            
if ($tzid = (string)$xdtend[0]['tzid']) {
                
$tz = new DateTimeZone($tzid);
            } else {
                
$tz null;
            }

            
// Since the VALUE parameter of both DTSTART and DTEND must be the same
            // we can assume we don't need to check the VALUE paramter of DTEND.
            
if ($isDateTime) {
                
$dtend Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtend[0],$tz);
            } else {
                
$dtend Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtend[0],$tz);
            }

        } 
        
        if (
is_null($dtend)) {
            
// The DTEND property was not found. We will first see if the event has a duration
            // property

            
$xduration $xml->xpath($currentXPath.'/c:duration');
            if (
count($xduration)) {
                
$duration Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]);

                
// Making sure that the duration is bigger than 0 seconds.
                
$tempDT = clone $dtstart;
                
$tempDT->modify($duration);
                if (
$tempDT $dtstart) {

                    
// use DTEND = DTSTART + DURATION 
                    
$dtend $tempDT;
                } else {
                    
// use DTEND = DTSTART
                    
$dtend $dtstart;
                }

            }
        }

        if (
is_null($dtend)) {
            if (
$isDateTime) {
                
// DTEND = DTSTART
                
$dtend $dtstart;
            } else {
                
// DTEND = DTSTART + 1 DAY
                
$dtend = clone $dtstart;
                
$dtend->modify('+1 day');
            }
        }
       
        if (!
is_null($currentFilter['time-range']['start']) && $currentFilter['time-range']['start'] >= $dtend)  return false;
        if (!
is_null($currentFilter['time-range']['end'])   && $currentFilter['time-range']['end']   <= $dtstart) return false;
        return 
true;
    
    }

    private function 
validateTimeRangeFilterForTodo(SimpleXMLElement $xml,$currentXPath,array $filter) {

        
// Gathering all relevant elements

        
$dtStart null;
        
$duration null;
        
$due null;
        
$completed null;
        
$created null;

        
$xdt $xml->xpath($currentXPath.'/c:dtstart');
        if (
count($xdt)) {
            
// The dtstart can be both a date, or datetime property
            
if ((string)$xdt[0]['value']==='DATE') {
                
$isDateTime false;
            } else {
                
$isDateTime true;
            }

            
// Determining the timezone
            
if ($tzid = (string)$xdt[0]['tzid']) {
                
$tz = new DateTimeZone($tzid);
            } else {
                
$tz null;
            }
            if (
$isDateTime) {
                
$dtStart Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz);
            } else {
                
$dtStart Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]);
            }
        }

        
// Only need to grab duration if dtStart is set
        
if (!is_null($dtStart)) {

            
$xduration $xml->xpath($currentXPath.'/c:duration');
            if (
count($xduration)) {
                
$duration Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]);
            }

        }

        if (!
is_null($dtStart) && !is_null($duration)) {

            
// Comparision from RFC 4791:
            // (start <= DTSTART+DURATION) AND ((end > DTSTART) OR (end >= DTSTART+DURATION))

            
$end = clone $dtStart;
            
$end->modify($duration);

            if( (
is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $end) &&
                (
is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $dtStart || $filter['time-range']['end'] >= $end) ) {
                return 
true;
            } else {
                return 
false;
            }

        }

        
// Need to grab the DUE property
        
$xdt $xml->xpath($currentXPath.'/c:due');
        if (
count($xdt)) {
            
// The due property can be both a date, or datetime property
            
if ((string)$xdt[0]['value']==='DATE') {
                
$isDateTime false;
            } else {
                
$isDateTime true;
            }
            
// Determining the timezone
            
if ($tzid = (string)$xdt[0]['tzid']) {
                
$tz = new DateTimeZone($tzid);
            } else {
                
$tz null;
            }
            if (
$isDateTime) {
                
$due Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz);
            } else {
                
$due Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]);
            }
        }

        if (!
is_null($dtStart) && !is_null($due)) {

            
// Comparision from RFC 4791:
            // ((start < DUE) OR (start <= DTSTART)) AND ((end > DTSTART) OR (end >= DUE))
            
            
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due || $filter['time-range']['start'] < $dtstart) &&
                (
is_null($filter['time-range']['end'])   || $filter['time-range']['end'] >= $due) ) {
                return 
true;
            } else {
                return 
false;
            }

        }

        if (!
is_null($dtStart)) {
            
            
// Comparision from RFC 4791
            // (start <= DTSTART)  AND (end > DTSTART)
            
if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $dtStart) &&
                 (
is_null($filter['time-range']['end'])   || $filter['time-range']['end'] > $dtStart) ) {
                 return 
true;
            } else {
                return 
false;
            }

        }

        if (!
is_null($due)) {
            
            
// Comparison from RFC 4791
            // (start < DUE) AND (end >= DUE)
            
if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due) &&
                 (
is_null($filter['time-range']['end'])   || $filter['time-range']['end'] >= $due) ) {
                 return 
true;
            } else {
                return 
false;
            }

        }
        
// Need to grab the COMPLETED property
        
$xdt $xml->xpath($currentXPath.'/c:completed');
        if (
count($xdt)) {
            
$completed Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]);
        }
        
// Need to grab the CREATED property
        
$xdt $xml->xpath($currentXPath.'/c:created');
        if (
count($xdt)) {
            
$created Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]);
        }

        if (!
is_null($completed) && !is_null($created)) {
            
// Comparison from RFC 4791
            // ((start <= CREATED) OR (start <= COMPLETED)) AND ((end >= CREATED) OR (end >= COMPLETED))
            
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $created || $filter['time-range']['start'] <= $completed) &&
                (
is_null($filter['time-range']['end'])   || $filter['time-range']['end'] >= $created   || $filter['time-range']['end'] >= $completed)) {
                return 
true;
            } else {
                return 
false;
            }
        }

        if (!
is_null($completed)) {
            
// Comparison from RFC 4791
            // (start <= COMPLETED) AND (end  >= COMPLETED)
            
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $completed) &&
                (
is_null($filter['time-range']['end'])   || $filter['time-range']['end'] >= $completed)) {
                return 
true;
            } else {
                return 
false;
            }
        }

        if (!
is_null($created)) {
            
// Comparison from RFC 4791
            // (end > CREATED)
            
if( (is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $created) ) {
                return 
true;
            } else {
                return 
false;
            }
        }

        
// Everything else is TRUE
        
return true;

    }

    public function 
substringMatch($haystack$needle$collation) {

        switch(
$collation) {
            case 
'i;ascii-casemap' :
                
// default strtolower takes locale into consideration
                // we don't want this.
                
$haystack str_replace(range('a','z'), range('A','Z'), $haystack);
                
$needle str_replace(range('a','z'), range('A','Z'), $needle);
                return 
strpos($haystack$needle)!==false;

            case 
'i;octet' :
                return 
strpos($haystack$needle)!==false;
            
            default:
                throw new 
Sabre_DAV_Exception_BadRequest('Unknown collation: ' $collation);
        }                

    }

}
Онлайн: 1
Реклама