basic support for CalDAV Dropbox
authorCornelius Weiß <mail@corneliusweiss.de>
Tue, 3 Jun 2014 12:58:07 +0000 (14:58 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Sep 2014 09:26:36 +0000 (11:26 +0200)
* iCal versions < 10.8 use dropbox for event attachments

Change-Id: I0d88c84d5902e9522cc8c1db8a948f38346dab71
Reviewed-on: http://gerrit.tine20.com/customers/726
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Calendar/Frontend/CalDAV/PluginManagedAttachmentsTest.php
tests/tine20/Tinebase/Frontend/WebDAV/RecordTest.php
tine20/Calendar/Frontend/CalDAV/Dropbox.php [new file with mode: 0644]
tine20/Calendar/Frontend/CalDAV/PluginManagedAttachments.php
tine20/Calendar/Frontend/WebDAV.php
tine20/Tinebase/Frontend/WebDAV/Record.php

index 2834536..c78b0ed 100644 (file)
@@ -71,6 +71,36 @@ class Calendar_Frontend_CalDAV_PluginManagedAttachmentsTest extends TestCase
         $this->assertEquals('calendarManagedAttachments', $pluginName);
     }
     
+    public function testGetProperties()
+    {
+        $body = '<?xml version="1.0" encoding="UTF-8"?>
+             <A:propfind xmlns:A="DAV:">
+               <A:prop>
+                 <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+               </A:prop>
+             </A:propfind>';
+    
+        $request = new Sabre\HTTP\Request(array(
+                'REQUEST_METHOD' => 'PROPFIND',
+                'REQUEST_URI'    => '/' . Tinebase_WebDav_PrincipalBackend::PREFIX_USERS . '/' . Tinebase_Core::getUser()->contact_id
+        ));
+        $request->setBody($body);
+    
+        $this->server->httpRequest = $request;
+        $this->server->exec();
+        
+        $responseDoc = new DOMDocument();
+        $responseDoc->loadXML($this->response->body);
+        //$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
+        $xpath = new DomXPath($responseDoc);
+        $xpath->registerNamespace('cal', 'urn:ietf:params:xml:ns:caldav');
+        $xpath->registerNamespace('cs',  'http://calendarserver.org/ns/');
+        
+        $nodes = $xpath->query('//d:multistatus/d:response/d:propstat/d:prop/cs:dropbox-home-URL/d:href');
+        $dropboxUrl = $nodes->item(0)->nodeValue;
+        $this->assertTrue(!! strstr($dropboxUrl, 'dropbox'), $dropboxUrl);
+    }
+    
     /**
      * test testAddAttachment 
      */
@@ -241,7 +271,48 @@ class Calendar_Frontend_CalDAV_PluginManagedAttachmentsTest extends TestCase
         
         $attachment = $createdException->attachments->getFirstRecord();
         $this->assertEquals('agenda.html', $attachment->name);
-        
+    }
 
+    public function testDropBoxMKCol()
+    {
+        $event = $this->calDAVTests->testCreateEventWithInternalOrganizer();
+        
+        $body = '';
+        $request = new Sabre\HTTP\Request(array(
+                'REQUEST_METHOD' => 'MKCOL',
+                'REQUEST_URI'    => '/calendars/' . Tinebase_Core::getUser()->contact_id . '/dropbox/' . 
+                    $event->getRecord()->getId() . '.dropbox'
+        ));
+        $request->setBody($body);
+        
+        $this->server->httpRequest = $request;
+        $this->server->exec();
+        
+        // attachement folder is managed by tine20 itself
+        $this->assertEquals('HTTP/1.1 405 Method Not Allowed', $this->response->status);
+    }
+    
+    public function testDropBoxPut()
+    {
+        $event = $this->calDAVTests->testCreateEventWithInternalOrganizer();
+    
+        $request = new Sabre\HTTP\Request(array(
+                'REQUEST_METHOD' => 'PUT',
+                'REQUEST_URI'    => '/calendars/' . Tinebase_Core::getUser()->contact_id . '/dropbox/' .
+                $event->getRecord()->getId() . '.dropbox/agenda.txt'
+        ));
+        
+        $agenda = 'HELLO WORLD';
+        $request->setBody($agenda);
+    
+        $this->server->httpRequest = $request;
+        $this->server->exec();
+        
+//         echo $this->response->body;
+        $attachments = Tinebase_FileSystem_RecordAttachments::getInstance()
+        ->getRecordAttachments($event->getRecord());
+        
+        $this->assertEquals(1, $attachments->count());
+        $this->assertEquals('agenda.txt', $attachments[0]->name);
     }
 }
index 26bba0a..dd2a4af 100644 (file)
@@ -44,6 +44,19 @@ class Tinebase_Frontend_WebDAV_RecordTest extends TestCase
         $node = $this->_getWebDAVTree()->getNodeForPath('/webdav/Calendar/records/Calendar_Model_Event/' . $savedEvent->getId() . '/' . $tempFile->name);
         $this->assertEquals('text/plain', $node->getContentType());
         $this->assertEquals(17, $node->getSize());
+        
+        return $savedEvent;
+    }
+    
+    public function testListAttachments()
+    {
+        $savedEvent = $this->testDownloadAttachment();
+        
+        $children = $this->_getWebDAVTree()->getChildren('/webdav/Calendar/records/Calendar_Model_Event/' . $savedEvent->getId() . '/');
+        $node = $children[0];
+        
+        $this->assertEquals('text/plain', $node->getContentType());
+        $this->assertEquals(17, $node->getSize());
     }
     
     /**
diff --git a/tine20/Calendar/Frontend/CalDAV/Dropbox.php b/tine20/Calendar/Frontend/CalDAV/Dropbox.php
new file mode 100644 (file)
index 0000000..34203d7
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Calendar
+ * @subpackage  Frontend
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * class to handle droipbox in CalDAV tree
+ *
+ * NOTE implementing IACL does not help to get rid of acl checkbox in Lion's Calendar Edit Dialog
+ * 
+ * @package     Calendar
+ * @subpackage  Frontend
+ */
+class Calendar_Frontend_CalDAV_Dropbox extends \Sabre\DAV\Collection
+{
+    /**
+     * @var Tinebase_Model_FullUser
+     */
+    protected $_user;
+    
+    const NAME='dropbox';
+    
+    public function __construct($_userId)
+    {
+        $this->_user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : Tinebase_User::getInstance()->get($_userId);
+    }
+    
+    /**
+     * (non-PHPdoc)
+     * @see Sabre\DAV\Collection::createDirectory()
+     */
+    public function createDirectory($name)
+    {
+        // client tries to add directory but it already exists => nothing to do
+    }
+    
+    /**
+     * (non-PHPdoc)
+     * @see Sabre\DAV\Collection::getChild()
+     */
+    public function getChild($_name)
+    {
+        $eventId = $this->_getIdFromName($_name);
+        $path = 'Calendar/records/Calendar_Model_Event/' . $eventId;
+        
+        return new Tinebase_Frontend_WebDAV_Record($path);
+    }
+    
+    /**
+     * @see Sabre\DAV\Collection::getChildren()
+     */
+    function getChildren()
+    {
+        // we don't allow dropbox listing yet
+        return array();
+    }
+    
+    /**
+     * Returns the name of the node
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return self::NAME;
+    }
+    
+    /**
+     * get id from name => strip of everything after last dot
+     * 
+     * @param  string  $_name  the name for example vcard.vcf
+     * @return string
+     */
+    protected function _getIdFromName($_name)
+    {
+        $id = ($pos = strrpos($_name, '.')) === false ? $_name : substr($_name, 0, $pos);
+        
+        return $id;
+    }
+}
index 091d661..e37d22b 100644 (file)
@@ -63,6 +63,40 @@ class Calendar_Frontend_CalDAV_PluginManagedAttachments extends \Sabre\DAV\Serve
         $this->server = $server;
 
         $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler'));
+        
+        $server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
+        
+        $server->xmlNamespaces[\Sabre\CalDAV\Plugin::NS_CALENDARSERVER] = 'cs';
+        
+        $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
+        
+    }
+    
+    /**
+     * beforeGetProperties
+     *
+     * 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 string $path
+     * @param \Sabre\DAV\INode $node
+     * @param array $requestedProperties
+     * @param array $returnedProperties
+     * @return void
+     */
+    public function beforeGetProperties($path, \Sabre\DAV\INode $node, &$requestedProperties, &$returnedProperties) {
+        if ($node instanceof \Sabre\DAVACL\IPrincipal) {
+            // dropbox-home-URL property
+            $scheduleProp = '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}dropbox-home-URL';
+            if (in_array($scheduleProp,$requestedProperties)) {
+                $principalId = $node->getName();
+                $dropboxPath = \Sabre\CalDAV\Plugin::CALENDAR_ROOT . '/' . $principalId . '/dropbox';
+                
+                unset($requestedProperties[array_search($scheduleProp, $requestedProperties)]);
+                $returnedProperties[200][$scheduleProp] = new \Sabre\DAV\Property\Href($dropboxPath);
+            }
+        }
     }
     
     /**
index f73412f..2aae0b8 100644 (file)
@@ -46,7 +46,7 @@ class Calendar_Frontend_WebDAV extends Tinebase_WebDav_Collection_AbstractContai
     public function getChild($name)
     {
         // do this only for caldav requests
-        if ($this->_useIdAsName && count($this->_getPathParts()) == 2 && in_array($name, array('inbox', 'outbox'))) {
+        if ($this->_useIdAsName && count($this->_getPathParts()) == 2 && in_array($name, array('inbox', 'outbox', 'dropbox'))) {
             switch ($name) {
                 case 'inbox':
                     return new Calendar_Frontend_CalDAV_ScheduleInbox(Tinebase_Core::getUser());
@@ -57,6 +57,11 @@ class Calendar_Frontend_WebDAV extends Tinebase_WebDav_Collection_AbstractContai
                     return new \Sabre\CalDAV\Schedule\Outbox('principals/users/' . Tinebase_Core::getUser()->contact_id);
                     
                     break;
+                    
+                case 'dropbox':
+                    return new Calendar_Frontend_CalDAV_Dropbox(Tinebase_Core::getUser());
+                    
+                    break;
             }
         }
         
index 2ebf7a4..c2ac25d 100644 (file)
@@ -56,12 +56,10 @@ class Tinebase_Frontend_WebDAV_Record implements Sabre\DAV\ICollection
         
         $children = array();
         
-        // TODO throw exception?
-            
         // Loop through record attachments / record data
-//         foreach ($this->_record->attachments as $attachment) {
-//             $children[] = $this->getChild($attachment->path);
-//         }
+        foreach ($this->_record->attachments as $attachment) {
+            $children[] = $this->getChild($attachment->name);
+        }
         
         return $children;
     }
@@ -94,7 +92,15 @@ class Tinebase_Frontend_WebDAV_Record implements Sabre\DAV\ICollection
      */
     function createFile($name, $data = null)
     {
-        // TODO throw exception?
+        if (is_resource($data)) {
+            // convert to rewindable stream
+            $rewindableStream = fopen('php://temp','r+');
+            stream_copy_to_stream($data, $rewindableStream);
+            rewind($rewindableStream);
+            $data = $rewindableStream;
+        }
+        
+        Tinebase_FileSystem_RecordAttachments::getInstance()->addRecordAttachment($this->_record, $name, $data);
     }
 
     /**