text:
<?php

namespace Sabre\CalDAV;

use DateTimeZone;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\VObject;
use Sabre\HTTP;
use Sabre\Uri;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

class Plugin extends 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 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 DAV\Server
     */
    protected $server;

    /**
     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
     * capping it to 10M here.
     */
    protected $maxResourceSize = 10000000;

    /**
     * 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
     */
    function getHTTPMethods($uri) {

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

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

        if ($node instanceof DAV\IExtendedCollection) {
            try {
                $node->getChild($name);
            } catch (DAV\Exception\NotFound $e) {
                return ['MKCALENDAR'];
            }
        }
        return [];

    }

    /**
     * Returns the path to a principal's calendar home.
     *
     * The return url must not end with a slash.
     * This function should return null in case a principal did not have
     * a calendar home.
     *
     * @param string $principalUrl
     * @return string
     */
    function getCalendarHomeForPrincipal($principalUrl) {

        // The default behavior for most sabre/dav servers is that there is a
        // principals root node, which contains users directly under it.
        //
        // This function assumes that there are two components in a principal
        // path. If there's more, we don't return a calendar home. This
        // excludes things like the calendar-proxy-read principal (which it
        // should).
        $parts = explode('/', trim($principalUrl, '/'));
        if (count($parts) !== 2) return;
        if ($parts[0] !== 'principals') return;

        return self::CALENDAR_ROOT . '/' . $parts[1];

    }

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

        return ['calendar-access', 'calendar-proxy'];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'caldav';

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        $node = $this->server->tree->getNodeForPath($uri);

        $reports = [];
        if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
            $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
            $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
        }
        if ($node instanceof ICalendar) {
            $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
        }
        // iCal has a bug where it assumes that sync support is enabled, only
        // if we say we support it on the calendar-home, even though this is
        // not actually the case.
        if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
            $reports[] = '{DAV:}sync-collection';
        }
        return $reports;

    }

    /**
     * Initializes the plugin
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;

        $server->on('method:MKCALENDAR',   [$this, 'httpMkCalendar']);
        $server->on('report',              [$this, 'report']);
        $server->on('propFind',            [$this, 'propFind']);
        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
        $server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
        $server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
        $server->on('afterMethod:GET',     [$this, 'httpAfterGET']);
        $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);

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

        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';

        $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';

        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';

        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',
            '{' . self::NS_CALDAV . '}calendar-data',

            // CalendarServer extensions
            '{' . self::NS_CALENDARSERVER . '}getctag',
            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'

        );

        if ($aclPlugin = $server->getPlugin('acl')) {
            $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
        }
    }

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

        switch ($reportName) {
            case '{' . self::NS_CALDAV . '}calendar-multiget' :
                $this->server->transactionType = 'report-calendar-multiget';
                $this->calendarMultiGetReport($report);
                return false;
            case '{' . self::NS_CALDAV . '}calendar-query' :
                $this->server->transactionType = 'report-calendar-query';
                $this->calendarQueryReport($report);
                return false;
            case '{' . self::NS_CALDAV . '}free-busy-query' :
                $this->server->transactionType = 'report-free-busy-query';
                $this->freeBusyQueryReport($report);
                return false;

        }

    }

    /**
     * This function handles the MKCALENDAR HTTP method, which creates
     * a new calendar.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {

        $body = $request->getBodyAsString();
        $path = $request->getPath();

        $properties = [];

        if ($body) {

            try {
                $mkcalendar = $this->server->xml->expect(
                    '{urn:ietf:params:xml:ns:caldav}mkcalendar',
                    $body
                );
            } catch (\Sabre\Xml\ParseException $e) {
                throw new BadRequest($e->getMessage(), null, $e);
            }
            $properties = $mkcalendar->getProperties();

        }

        // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
        // subscriptions. Before that it used MKCOL which was the correct way
        // to do this.
        //
        // If the body had a {DAV:}resourcetype, it means we stumbled upon this
        // request, and we simply use it instead of the pre-defined list.
        if (isset($properties['{DAV:}resourcetype'])) {
            $resourceType = $properties['{DAV:}resourcetype']->getValue();
        } else {
            $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
        }

        $this->server->createCollection($path, new MkCol($resourceType, $properties));

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

        // This breaks the method chain.
        return false;
    }

    /**
     * PropFind
     *
     * This method handler is invoked before any after properties for a
     * resource are fetched. This allows us to add in any CalDAV specific
     * properties.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFind(DAV\PropFind $propFind, DAV\INode $node) {

        $ns = '{' . self::NS_CALDAV . '}';

        if ($node instanceof ICalendarObjectContainer) {

            $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
            $propFind->handle($ns . 'supported-calendar-data', function() {
                return new Xml\Property\SupportedCalendarData();
            });
            $propFind->handle($ns . 'supported-collation-set', function() {
                return new Xml\Property\SupportedCollationSet();
            });

        }

        if ($node instanceof DAVACL\IPrincipal) {

            $principalUrl = $node->getPrincipalUrl();

            $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {

                $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
                if (is_null($calendarHomePath)) return null;
                return new LocalHref($calendarHomePath . '/');

            });
            // The calendar-user-address-set property is basically mapped to
            // the {DAV:}alternate-URI-set property.
            $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
                $addresses = $node->getAlternateUriSet();
                $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
                return new LocalHref($addresses);
            });
            // For some reason somebody thought it was a good idea to add
            // another one of these properties. We're supporting it too.
            $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
                $addresses = $node->getAlternateUriSet();
                $emails = [];
                foreach ($addresses as $address) {
                    if (substr($address, 0, 7) === 'mailto:') {
                        $emails[] = substr($address, 7);
                    }
                }
                return new Xml\Property\EmailAddressSet($emails);
            });

            // These two properties are shortcuts for ical to easily find
            // other principals this principal has access to.
            $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
            $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';

            if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {

                $aclPlugin = $this->server->getPlugin('acl');
                $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
                $readList = [];
                $writeList = [];

                foreach ($membership as $group) {

                    $groupNode = $this->server->tree->getNodeForPath($group);

                    $listItem = Uri\split($group)[0] . '/';

                    // If the node is either ap proxy-read or proxy-write
                    // group, we grab the parent principal and add it to the
                    // list.
                    if ($groupNode instanceof Principal\IProxyRead) {
                        $readList[] = $listItem;
                    }
                    if ($groupNode instanceof Principal\IProxyWrite) {
                        $writeList[] = $listItem;
                    }

                }

                $propFind->set($propRead, new LocalHref($readList));
                $propFind->set($propWrite, new LocalHref($writeList));

            }

        } // instanceof IPrincipal

        if ($node instanceof ICalendarObject) {

            // The calendar-data property is not supposed to be a 'real'
            // property, but in large chunks of the spec it does act as such.
            // Therefore we simply expose it as a property.
            $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
                $val = $node->get();
                if (is_resource($val))
                    $val = stream_get_contents($val);

                // Taking out \r to not screw up the xml output
                return str_replace("\r", "", $val);

            });

        }

    }

    /**
     * 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 CalendarMultiGetReport $report
     * @return void
     */
    function calendarMultiGetReport($report) {

        $needsJson = $report->contentType === 'application/calendar+json';

        $timeZones = [];
        $propertyList = [];

        $paths = array_map(
            [$this->server, 'calculateUri'],
            $report->hrefs
        );

        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {

            if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
                $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);

                if ($report->expand) {
                    // We're expanding, and for that we need to figure out the
                    // calendar's timezone.
                    list($calendarPath) = Uri\split($uri);
                    if (!isset($timeZones[$calendarPath])) {
                        // Checking the calendar-timezone property.
                        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
                        $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
                        if (isset($tzResult[$tzProp])) {
                            // This property contains a VCALENDAR with a single
                            // VTIMEZONE.
                            $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
                            $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
                        } else {
                            // Defaulting to UTC.
                            $timeZone = new DateTimeZone('UTC');
                        }
                        $timeZones[$calendarPath] = $timeZone;
                    }

                    $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
                }
                if ($needsJson) {
                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                } else {
                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                }
                // Destroy circular references so PHP will garbage collect the
                // object.
                $vObject->destroy();
            }

            $propertyList[] = $objProps;

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));

    }

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

        $path = $this->server->getRequestUri();

        $needsJson = $report->contentType === 'application/calendar+json';

        $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
        $depth = $this->server->getHTTPDepth(0);

        // The default result is an empty array
        $result = [];

        $calendarTimeZone = null;
        if ($report->expand) {
            // We're expanding, and for that we need to figure out the
            // calendar's timezone.
            $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
            $tzResult = $this->server->getProperties($path, [$tzProp]);
            if (isset($tzResult[$tzProp])) {
                // This property contains a VCALENDAR with a single
                // VTIMEZONE.
                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();

                // Destroy circular references so PHP will garbage collect the
                // object.
                $vtimezoneObj->destroy();
            } else {
                // Defaulting to UTC.
                $calendarTimeZone = new DateTimeZone('UTC');
            }
        }

        // The calendarobject was requested directly. In this case we handle
        // this locally.
        if ($depth == 0 && $node instanceof ICalendarObject) {

            $requestedCalendarData = true;
            $requestedProperties = $report->properties;

            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;
            }

            $properties = $this->server->getPropertiesForPath(
                $path,
                $requestedProperties,
                0
            );

            // This array should have only 1 element, the first calendar
            // object.
            $properties = current($properties);

            // If there wasn't any calendar-data returned somehow, we ignore
            // this.
            if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {

                $validator = new CalendarQueryValidator();

                $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                if ($validator->validate($vObject, $report->filters)) {

                    // If the client didn't require the calendar-data property,
                    // we won't give it back.
                    if (!$requestedCalendarData) {
                        unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                    } else {

                        if ($report->expand) {
                            $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
                        }
                        if ($needsJson) {
                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                        } elseif ($report->expand) {
                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                        }
                    }

                    $result = [$properties];

                }
                // Destroy circular references so PHP will garbage collect the
                // object.
                $vObject->destroy();

            }

        }

        if ($node instanceof ICalendarObjectContainer && $depth === 0) {

            if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                // Microsoft clients incorrectly supplied depth as 0, when it actually
                // should have set depth to 1. We're implementing a workaround here
                // to deal with this.
                //
                // This targets at least the following clients:
                //   Windows 10
                //   Windows Phone 8, 10
                $depth = 1;
            } else {
                throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
            }

        }

        // If we're dealing with a calendar, the calendar itself is responsible
        // for the calendar-query.
        if ($node instanceof ICalendarObjectContainer && $depth == 1) {

            $nodePaths = $node->calendarQuery($report->filters);

            foreach ($nodePaths as $path) {

                list($properties) =
                    $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);

                if (($needsJson || $report->expand)) {
                    $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);

                    if ($report->expand) {
                        $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
                    }

                    if ($needsJson) {
                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                    } else {
                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                    }

                    // Destroy circular references so PHP will garbage collect the
                    // object.
                    $vObject->destroy();
                }
                $result[] = $properties;

            }

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));

    }

    /**
     * This method is responsible for parsing the request and generating the
     * response for the CALDAV:free-busy-query REPORT.
     *
     * @param Xml\Request\FreeBusyQueryReport $report
     * @return void
     */
    protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {

        $uri = $this->server->getRequestUri();

        $acl = $this->server->getPlugin('acl');
        if ($acl) {
            $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
        }

        $calendar = $this->server->tree->getNodeForPath($uri);
        if (!$calendar instanceof ICalendar) {
            throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
        }

        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';

        // Figuring out the default timezone for the calendar, for floating
        // times.
        $calendarProps = $this->server->getProperties($uri, [$tzProp]);

        if (isset($calendarProps[$tzProp])) {
            $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
            $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
            // Destroy circular references so PHP will garbage collect the object.
            $vtimezoneObj->destroy();
        } else {
            $calendarTimeZone = new DateTimeZone('UTC');
        }

        // Doing a calendar-query first, to make sure we get the most
        // performance.
        $urls = $calendar->calendarQuery([
            'name'         => 'VCALENDAR',
            'comp-filters' => [
                [
                    'name'           => 'VEVENT',
                    'comp-filters'   => [],
                    'prop-filters'   => [],
                    'is-not-defined' => false,
                    'time-range'     => [
                        'start' => $report->start,
                        'end'   => $report->end,
                    ],
                ],
            ],
            'prop-filters'   => [],
            'is-not-defined' => false,
            'time-range'     => null,
        ]);

        $objects = array_map(function($url) use ($calendar) {
            $obj = $calendar->getChild($url)->get();
            return $obj;
        }, $urls);

        $generator = new VObject\FreeBusyGenerator();
        $generator->setObjects($objects);
        $generator->setTimeRange($report->start, $report->end);
        $generator->setTimeZone($calendarTimeZone);
        $result = $generator->getResult();
        $result = $result->serialize();

        $this->server->httpResponse->setStatus(200);
        $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
        $this->server->httpResponse->setHeader('Content-Length', strlen($result));
        $this->server->httpResponse->setBody($result);

    }

    /**
     * This method is triggered before a file gets updated with new content.
     *
     * This plugin uses this method to ensure that CalDAV objects receive
     * valid calendar data.
     *
     * @param string $path
     * @param DAV\IFile $node
     * @param resource $data
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {

        if (!$node instanceof ICalendarObject)
            return;

        // We're onyl interested in ICalendarObject nodes that are inside of a
        // real calendar. This is to avoid triggering validation and scheduling
        // for non-calendars (such as an inbox).
        list($parent) = Uri\split($path);
        $parentNode = $this->server->tree->getNodeForPath($parent);

        if (!$parentNode instanceof ICalendar)
            return;

        $this->validateICalendar(
            $data,
            $path,
            $modified,
            $this->server->httpRequest,
            $this->server->httpResponse,
            false
        );

    }

    /**
     * This method is triggered before a new file is created.
     *
     * This plugin uses this method to ensure that newly created calendar
     * objects contain valid calendar data.
     *
     * @param string $path
     * @param resource $data
     * @param DAV\ICollection $parentNode
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {

        if (!$parentNode instanceof ICalendar)
            return;

        $this->validateICalendar(
            $data,
            $path,
            $modified,
            $this->server->httpRequest,
            $this->server->httpResponse,
            true
        );

    }

    /**
     * Checks if the submitted iCalendar data is in fact, valid.
     *
     * An exception is thrown if it's not.
     *
     * @param resource|string $data
     * @param string $path
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @param RequestInterface $request The http request.
     * @param ResponseInterface $response The http response.
     * @param bool $isNew Is the item a new one, or an update.
     * @return void
     */
    protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {

        // If it's a stream, we convert it to a string first.
        if (is_resource($data)) {
            $data = stream_get_contents($data);
        }

        $before = $data;

        try {

            // If the data starts with a [, we can reasonably assume we're dealing
            // with a jCal object.
            if (substr($data, 0, 1) === '[') {
                $vobj = VObject\Reader::readJson($data);

                // Converting $data back to iCalendar, as that's what we
                // technically support everywhere.
                $data = $vobj->serialize();
                $modified = true;
            } else {
                $vobj = VObject\Reader::read($data);
            }

        } catch (VObject\ParseException $e) {

            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());

        }

        if ($vobj->name !== 'VCALENDAR') {
            throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
        }

        $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';

        // Get the Supported Components for the target calendar
        list($parentPath) = Uri\split($path);
        $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);

        if (isset($calendarProperties[$sCCS])) {
            $supportedComponents = $calendarProperties[$sCCS]->getValue();
        } else {
            $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
        }

        $foundType = null;

        foreach ($vobj->getComponents() as $component) {
            switch ($component->name) {
                case 'VTIMEZONE' :
                    continue 2;
                case 'VEVENT' :
                case 'VTODO' :
                case 'VJOURNAL' :
                    $foundType = $component->name;
                    break;
            }

        }

        if (!$foundType || !in_array($foundType, $supportedComponents)) {
            throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
        }

        $options = VObject\Node::PROFILE_CALDAV;
        $prefer = $this->server->getHTTPPrefer();

        if ($prefer['handling'] !== 'strict') {
            $options |= VObject\Node::REPAIR;
        }

        $messages = $vobj->validate($options);

        $highestLevel = 0;
        $warningMessage = null;

        // $messages contains a list of problems with the vcard, along with
        // their severity.
        foreach ($messages as $message) {

            if ($message['level'] > $highestLevel) {
                // Recording the highest reported error level.
                $highestLevel = $message['level'];
                $warningMessage = $message['message'];
            }
            switch ($message['level']) {

                case 1 :
                    // Level 1 means that there was a problem, but it was repaired.
                    $modified = true;
                    break;
                case 2 :
                    // Level 2 means a warning, but not critical
                    break;
                case 3 :
                    // Level 3 means a critical error
                    throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);

            }

        }
        if ($warningMessage) {
            $response->setHeader(
                'X-Sabre-Ew-Gross',
                'iCalendar validation warning: ' . $warningMessage
            );
        }

        // We use an extra variable to allow event handles to tell us wether
        // the object was modified or not.
        //
        // This helps us determine if we need to re-serialize the object.
        $subModified = false;

        $this->server->emit(
            'calendarObjectChange',
            [
                $request,
                $response,
                $vobj,
                $parentPath,
                &$subModified,
                $isNew
            ]
        );

        if ($modified || $subModified) {
            // An event handler told us that it modified the object.
            $data = $vobj->serialize();

            // Using md5 to figure out if there was an *actual* change.
            if (!$modified && strcmp($data, $before) !== 0) {
                $modified = true;
            }

        }

        // Destroy circular references so PHP will garbage collect the object.
        $vobj->destroy();

    }

    /**
     * This method is triggered whenever a subsystem reqeuests the privileges
     * that are supported on a particular node.
     *
     * @param INode $node
     * @param array $supportedPrivilegeSet
     */
    function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {

        if ($node instanceof ICalendar) {
            $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
                'abstract'   => false,
                'aggregates' => [],
            ];
        }
    }

    /**
     * This method is used to generate HTML output for the
     * DAV\Browser\Plugin. This allows us to generate an interface users
     * can use to create new calendars.
     *
     * @param DAV\INode $node
     * @param string $output
     * @return bool
     */
    function htmlActionsPanel(DAV\INode $node, &$output) {

        if (!$node instanceof CalendarHome)
            return;

        $output .= '<tr><td colspan="2"><form method="post" action="">
            <h3>Create new calendar</h3>
            <input type="hidden" name="sabreAction" value="mkcol" />
            <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
            <label>Name (uri):</label> <input type="text" name="name" /><br />
            <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
            <input type="submit" value="create" />
            </form>
            </td></tr>';

        return false;

    }

    /**
     * This event is triggered after GET requests.
     *
     * This is used to transform data into jCal, if this was requested.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpAfterGet(RequestInterface $request, ResponseInterface $response) {

        if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
            return;
        }

        $result = HTTP\Util::negotiate(
            $request->getHeader('Accept'),
            ['text/calendar', 'application/calendar+json']
        );

        if ($result !== 'application/calendar+json') {
            // Do nothing
            return;
        }

        // Transforming.
        $vobj = VObject\Reader::read($response->getBody());

        $jsonBody = json_encode($vobj->jsonSerialize());
        $response->setBody($jsonBody);

        // Destroy circular references so PHP will garbage collect the object.
        $vobj->destroy();

        $response->setHeader('Content-Type', 'application/calendar+json');
        $response->setHeader('Content-Length', strlen($jsonBody));

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for CalDAV (rfc4791)',
            'link'        => 'http://sabre.io/dav/caldav/',
        ];

    }

}

redpajama_set_name: RedPajamaGithub


This table shows consumer price developments as measured by the Harmonised Index of Consumer Prices (HICP) for the euro area and the euro area countries. The figures shown are index values. The index reference year is 2015=100. A value of 110, for example, indicates that prices have increased by 10% since 2015. The breakdown is in accordance with the classification of individual consumption by purpose adapted to the needs of the HICP (ECOICOP). For more information on this classification please refer to the Eurostat HICP website. The breakdown shown in the table is the one mainly used by the ECB.
1. Overall index 104.36 . . . . . . . . . . . . . . . . . . .
1.1. Goods . . . . . . . . . . . . . . . . . . . .
1.1.1. Processed food incl. alcohol and tobacco 105.91 . . . . . . . . . . . . . . . . . . .
1.1.2. Unprocessed food 107.12 . . . . . . . . . . . . . . . . . . .
1.1.3. Industrial goods excluding energy 101.90 . . . . . . . . . . . . . . . . . . .
1.1.4. Energy 107.36 . . . . . . . . . . . . . . . . . . .
1.2. Services 104.34 . . . . . . . . . . . . . . . . . . .
2. Overall index excluding energy and unprocessed food 103.87 . . . . . . . . . . . . . . . . . . .
* These aggregates are compiled by Eurostat based on the most detailed level of the classification of individual consumption by purpose (ECOICOP). These categories provide an alternative presentation of HICP data which are useful for economic analysis. They are mainly used by the ECB.


text:
Q: attachment is not sent with phpmailer I'm trying to send an attachment with the phpMailer script.
Everything is working correctly since my email is sent correctly, however I do have a problem with an attachment that is not sent.
HTML part:

   Attachment :
   <input name="doc" type="file">

PHP:
<?php
require 'PHPMailerAutoload.php';

$mail = new PHPMailer;

$mail->isSMTP();
$mail->Host = '*****';
$mail->SMTPAuth = true;
$mail->Port = 465;
$mail->Username = '*****';
$mail->Password = '*****';
$mail->SMTPSecure = 'ssl';

$mail->From = $_POST["name"];
$mail->FromName  = 'Your Name';
$mail->Subject   = 'Message Subject';
$mail->addAddress('*****');

$mail->addAttachment($_FILES["doc"]);
$mail->isHTML(true);

$mail->Subject = 'Here is the subject';
$mail->Body    = "You got a new message from your website :

    Name:  $_POST[name]
    Company:  $_POST[company]
    Phone:  $_POST[phone]
    Email:  $_POST[email]
    Message:  $_POST[message]";

if(!$mail->send()) {
   echo 'Message could not be sent.';
   echo 'Mailer Error: ' . $mail->ErrorInfo;
   exit;
}

?>

<!DOCTYPE HTML>

<title>Thanks!</title>

 Thank you! We will get back to you soon.

A: Try:

    if (isset($_FILES['doc']) &&
        $_FILES['doc']['error'] == UPLOAD_ERR_OK) {
        $mail->AddAttachment($_FILES['doc']['tmp_name'],
                             $_FILES['doc']['name']);
    }

Basic example can also be found [here](https://code.google.com/a/apache-extras.org/p/phpmailer/wiki/AdvancedMail).

The function definition for `AddAttachment` is:

    public function AddAttachment($path,
                                  $name = '',
                                  $encoding = 'base64',
                                  $type = 'application/octet-stream')

A: Please have a look about how PHP file uploads work. This array key:
$_FILES["doc"]

... will never contain a file. At most, it'll contain an array with information about where to find the file.

A: Untested solution, but should work:
change
$mail->addAttachment($_FILES["doc"]);

to
$mail->addAttachment($_FILES["doc"]["tmp_name"], $_FILES["doc"]["name"], "base64", $_FILES["doc"]["tmp_type"]);

However, please consider learning php upload file handling as already commented by others. Don't use user input without validation. Never!

redpajama_set_name: RedPajamaStackExchange


Compact and very rigid floodlight.
Due to the small damage to the housing of the flood light is not 100% waterproof.
Therefore, the device may be used only in covered areas or interior.
Ideal for: workshops, garages, warehouses, as lighting for cleaning, for renovations, stable etc.
for sodium lamp NAV SON 70W (yellow) or metal halide HTI 70 watts (white).
This light source has an excellent efficiency.
The fixture has a black, corrosion-resistant plastic housing with an adjustable mounting bracket.
Dimensions: W 280mm, H 300mm, D 230mm.


It's a quiet morning so far on radar, but we are watching a few showers out in the Gulf right now that will try to move on shore in some of our western counties of the Panhandle this morning. Otherwise, most will wait for the midday or afternoon for a shot at some shower or thunderstorm activity today as a cold front heads down into the Southeast today.
The main concern we'll have out the door this morning is a bit of patchy dense fog developing. It may last in some spots through about 8am before lifting out to a bit of cloud cover during the Morning Commute.
Temperatures are mild and feeling a bit damp outside due to the high humidity. We'll see similar highs to where we've been over the past several days in the upper 70s to low 80s.
The cold front will stir up a few passing showers and possibly a few rumbles of thunder today. They'll likely develop first out to the west in the mid to late morning and head east throughout the day. We may be able to develop a couple rounds of passing showers or thunderstorms in this fashion today ahead of the front.
Once the front passes through tonight we'll be able to clear out a lot of our moisture and pull in some colder temperatures for the weekend. Winds will start to turn breezy from the north through this evening and stay breezy throughout the day tomorrow.
With a mix of sun and clouds on Saturday, temperatures will start out in the low 50s and only reach the low 60s in the afternoon! We'll see another partly cloudy day on Sunday with a very cold morning as we start out in the 40s and only rebound in the afternoon into the upper 60s.
Get out the warm clothes this weekend!
For today, partly to mostly cloudy skies with passing showers or a few thunderstorms. Highs today will reach the upper 70s to low 80s. A few showers may linger into the evening as breezy northerly winds take over heading into tonight. Your 7 Day Forecast will usher in some colder weather in for the weekend as skies quiet down to a mix of sun and clouds.


text:
Sales Service Body Parts
Sat: 8:00a - 12:30p
7310 East Kellogg Avenue • Wichita KS 67207
7310 East Kellogg Avenue
Wichita KS 67207
Ford Special Program
Transit Configurator
CNG Payback Estimator
2019 Ford F-650 & F-750
Ford+Alexa
Why Buy From Rusty Eck Ford?
Equipo de habla hispana
About Les Eck
Adopt-A-Cause
2019 Ford F-150 XL Truck For Sale In Wichita KS
9T0957
1FTEW1EP1KKC97626
Rusty Eck Discount:
Rusty Eck Price:
Shows all available incentives. May not be combined with each other and not everyone will qualify1
Military Appreciation Bonus Cash
First Responder Bonus Cash
Lease From:
Climb inside our great looking 2019 Ford F-150 XL SuperCrew 4X4 that is ready to roll in Magnetic! Powered by a TurboCharged 2.7 Liter EcoBoost V6 that offers 325hp while connected to an innovative 10 Speed Automatic transmission for amazing passing authority. You'll dominate the road in this Four Wheel Drive while providing near 26mpg on the highway. Our XL sets the tone and lets you know this truck means business. Climb inside the well-thought-out XL cabin and settle into the durable cloth seating. You'll be comfortable and content with air-conditioning, a tilt-and-telescoping steering wheel, an AM/FM Stereo with auxiliary input, Bluetooth, and a center-stack screen with audio controls. Buckle-up, look around and enjoy this solid, composed ride! Drive with confidence knowing this is a lean muscular machine with a high-strength military grade aluminum-alloy body and high-strength steel frame. Ford adds anti-lock disc brakes, stability/traction control, trailer sway control, front-seat side impact airbags and full-length side curtain airbags to help you avoid and manage challenging driving situations. Our F-150 meets the challenges of performance, efficiency and dependability so get behind the wheel today. Print this page and call us Now... We Know You Will Enjoy Your Test Drive Towards Ownership! Absolutely Unbeatable! REFW Price includes: $750 - Bonus Customer Cash. Exp. 09/30/2019, $750 - Ford Credit Retail Bonus Customer Cash. Exp. 09/30/2019, $250 - Retail Bonus Customer Cash. Exp. 09/03/2019, $1,000 - Special Package Retail Customer Cash. Exp. 09/30/2019, $3,500 - F-Series Retail Customer Cash. Exp. 09/03/2019
The 2019 F-150 has to be many things to many people. From work truck to cruiser, off-road vehicle to boat hauler, there is an F-150 for virtually every task. As a result, the lineup is diverse. There are four engines available on the F-150, six trims and several body options.
The most basic engine is a 3.3L V6, which produces 295 horsepower, allowing for a tow rating of 7,600 pounds, which bests other competitors when equipped with their basic V6 engines. The next step up is a 2.7L turbocharged V6 that makes 325 horsepower and 400 lb-ft of torque. A bigger 3.5L turbocharged V6 makes 375 horsepower and 470 lb-ft of torque, helping the F-150 rate a class-leading towing spec of 12,200 pounds. Of course, a V8 is still available; it's a 5.0L unit making 395 horsepower and 400 lb-ft of torque. A high-output version of the 3.5L turbocharged V6 making 450 horsepower and 510 lb-ft of torque is new for 2019.
Trims include XL, XLT, Lariat, King Ranch, Platinum and Limited. The basic XL trim is available with three different cab sizes and three different bed lengths in either 2WD or 4WD configurations. Owing to its work-truck pretensions, the XL is relatively light on bells and whistles, with just basic utilities like an AM/FM radio and a 4.2-inch screen in the center stack.
The XLT, while still relatively basic, adds chrome bumpers, Ford's SYNC 3 system, fog lamps, an alarm, air conditioning, automatic headlamps, cruise control, keyless entry, power windows, a powered tailgate and power door locks.
Lariat-trimmed F-150s start to venture into more luxurious territory and are only available with extended or crew cab cabins. Standard equipment on the Lariat includes power folding door mirrors, a rearview camera, 18-inch wheels, ambient interior lighting, steering wheel-mounted audio and cruise controls, a power sliding rear window, an auto-dimming rearview mirror, leather-trimmed seats, a 10-way power adjustable driver's seat, SiriusXM satellite radio and an 8-inch touchscreen.
King Ranch versions come standard with the 5.0L V8 engine, a crew cab cabin and 2-tone exterior paint. Other features on the King Ranch include LED box lighting, LED headlamps, lots of exterior chrome, heated side mirrors with integrated turn signals and an auto-dimming feature on the driver's side, a remote starter, heated front and rear seats, wood interior trim and dual zone climate control.
The Platinum version is the urban cousin to the King Ranch trim. Like the King Ranch, it's only available with a crew cab and the V8. It also features a voice-activated navigation system, unique 20-inch polished aluminum wheels, LED side mirror spotlights, power deployable running boards, aluminum interior trim and a leather wrapped and heated steering wheel.
Finally, the Limited trim rides on 22-inch wheels and makes standard a whole range of convenience items that are optional on lesser trims. These include a 360-degree camera with split-view display, remote tailgate release, a twin-panel moonroof and more.
Efficient Turbocharged Engines
Available In A Variety Of Trims
Improved Gas Mileage
Rugged Durability
The Ford F-150 continues into 2019 without too many changes. The biggest news is the addition of a new high-output turbocharged V6. While the F-150 had a few different engine options including multiple turbocharged V6's, the standard F-150 never got the ballistic V6 from the Ford Raptor. That has now changed. Owners wanting 450 horsepower lodged in a more standard F-150, without all the raptor suspension can now have their wish.
The F-150 might be the most important vehicle to the Ford motor company. It continues to be one of their top-selling vehicles, encouraging Ford to put their best foot forward with the 2019 model. The F-150 competes with the Ram, the Chevrolet Silverado and the GMC Sierra 1500, among others, in the full-sized truck category. Ford is clearly pushing its new technology and new ideas with the F-150, offering plenty of options not available anywhere else in this class, such as a 360-degree camera and integrated loading ramps stowed in the pickup bed. A smaller, more fuel efficient EcoBoost engine is also available, which should further improve fuel economy while providing the same kind of horsepower that would normally be delivered by mid-grade V8 engines. At the same time, the F-150 continues to excel at traditional truck tasks, with a best in class towing capacity of 12,200 pounds when properly equipped.
Engine: 3.3L V6 PDFI -inc: auto start-stop technology and flex-fuel capability
Transmission: Electronic 6-Speed Automatic -inc: selectable drive modes: normal/tow-haul/sport
Wheels: 17" Silver Steel
Black Manual Side Mirrors w/Convex Spotter and Manual Folding
Radio: AM/FM Stereo w/6 Speakers -inc: auxiliary audio input jack (not available w/SYNC)
Radio w/Seek-Scan
Cloth 40/20/40 Front Seat -inc: 2-way manual driver/passenger adjustment and armrest
Interior Trim -inc: Cabback Insulator and Chrome Interior Accents
Instrument Panel Bin, Dashboard Storage, Driver / Passenger And Rear Door Bins
* All prices listed plus tax and applicable fees. While we make every effort to ensure the data listed here is correct, there may be instances where some of the factory rebates, incentives, options or vehicle features may be listed incorrectly as we get data from multiple data sources. PLEASE MAKE SURE to confirm the details of this vehicle (such as what factory rebates you may or may not qualify for) with the dealer to ensure its accuracy. Dealer cannot be held liable for data that is listed incorrectly.
New Used Commercial Videos Financing Service About Us Why Rusty Eck?
© 2019 Rusty Eck Ford | Privacy Policy
Search Rusty Eck Ford
Before You Go, Get Your Exclusive Offer

redpajama_set_name: RedPajamaCommonCrawl


text:
El torrent de la Baga Cerdana, a Moià conegut com a Riera de l'Om, és un torrent que discorre pel terme municipal de Monistrol de Calders, al Moianès.

Origen
Es forma per transformació de la riera de l'Om al sud-est de la Puntogaina, o del Pla de Puntogaina, i al nord-oest del Serrat de Baiones, al límit dels termes municipals de Moià i de Monistrol de Calders.

Curs

Des d'aquest lloc, el torrent de la Baga Cerdana emprèn per la vall de la Baga Cerdana, a migdia del Pla de la Puntogaimna, en direcció a ponent però decantant-se lleugerament cap al sud. Fa un meandre a l'entorn del Llosar, després del qual segueix un tros cap al nord-oest per, tot seguit, quan troba el Serrat de la Tirolena a ponent, trencar més de 90° cap al sud. Al cap d'un breu tram, arriba a l'espectacular Salt de la Baga Cerdana, també anomenat Salt de la Coma, des d'on, sempre cap al sud, continua cap a la Coma.

Passa a llevant d'el Pedregar, deixant a l'esquerra la Font Fresca, i, després de fer un darrer revolt, s'aboca en la Golarda al sud de la masia de la Coma.

Bibliografia

 ERILL I PINYOT, Gustau, GUAL I PURTÍ, Jordi (fotografies) i MANENT I ORISTRELL, Llibert (cartografia). Monistrol de Calders: El poble dels pagesos enginyers. Moià-Monistrol de Calders: Gràfiques Ister. Ajuntament de Monistrol de Calders, 2006. (Col·lecció Pedra i aigua, 1). DL. B-54.339/2006
 PERARNAU I LLORENS, Jaume i PIÑERO I SUBIRANA, Jordi. Monistrol de Calders. La seva història i el seu patrimoni. Monistrol de Calders: Ajuntament de Monistrol de Calders, 1993. Sense ISBN.

Baga Cerdana
Rius de la conca del Calders

redpajama_set_name: RedPajamaWikipedia