first commit
This commit is contained in:
commit
f239099d55
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/vendor/
|
25
composer.json
Normal file
25
composer.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "aerex/taskwarrior",
|
||||
"description": "A Baikal plugin for taskwarrior",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"task",
|
||||
"taskwarrior",
|
||||
"Baikal",
|
||||
"sabre"
|
||||
],
|
||||
"require": {
|
||||
"sabre/dav": "^3.2",
|
||||
"davidbadura/taskwarrior": "^3.0",
|
||||
"sabre/vobject": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit" : "> 4.8, <=6.0.0"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Aerex",
|
||||
"email": "aerex@aerex.me"
|
||||
}
|
||||
]
|
||||
}
|
2850
composer.lock
generated
Normal file
2850
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
865
libs/Plugin.php
Normal file
865
libs/Plugin.php
Normal file
@ -0,0 +1,865 @@
|
||||
<?php
|
||||
|
||||
namespace Aerex\Taskwarrior;
|
||||
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\VObject;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\Xml\ParseException;
|
||||
use Aerex\Taskwarrior\TaskwarriorVCalendarManger;
|
||||
use DavidBadura\Taskwarrior\TaskManager;
|
||||
use DavidBadura\Taskwarrior\Task;
|
||||
use DavidBadura\Taskwarrior\Recurring;
|
||||
use DavidBadura\Taskwarrior\Annotation;
|
||||
|
||||
/**
|
||||
* The plugin to interact with Baikal and Taskwarrior
|
||||
*
|
||||
*/
|
||||
class Taskwarrior extends ServerPlugin {
|
||||
|
||||
/**
|
||||
* Reference to server object.
|
||||
*
|
||||
* @var Server
|
||||
*/
|
||||
protected $server;
|
||||
private $TWCalManager;
|
||||
|
||||
/**
|
||||
* Sets up the plugin
|
||||
*
|
||||
* @param Server $server
|
||||
* @return void
|
||||
*/
|
||||
function initialize(Server $server) {
|
||||
|
||||
$this->server = $server;
|
||||
$this->$TWCalManager = new TaskwarriorCalendarEvent();
|
||||
$server->on('method:GET', [$this, 'httpGet']);
|
||||
$server->on('method:OPTIONS', [$this, 'httpOptions']);
|
||||
$server->on('method:HEAD', [$this, 'httpHead']);
|
||||
$server->on('method:DELETE', [$this, 'httpDelete']);
|
||||
$server->on('method:PROPFIND', [$this, 'httpPropFind']);
|
||||
$server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
|
||||
$server->on('method:PUT', [$this, 'httpPut']);
|
||||
$server->on('method:MKCOL', [$this, 'httpMkcol']);
|
||||
$server->on('method:MOVE', [$this, 'httpMove']);
|
||||
$server->on('method:COPY', [$this, 'httpCopy']);
|
||||
$server->on('method:REPORT', [$this, 'httpReport']);
|
||||
|
||||
$server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90);
|
||||
$server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200);
|
||||
$server->on('propFind', [$this, 'propFind']);
|
||||
$server->on('propFind', [$this, 'propFindNode'], 120);
|
||||
$server->on('propFind', [$this, 'propFindLate'], 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'taskwarrior';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will parse a calendar object and create an new task in taskwarrior
|
||||
*
|
||||
* @param VCalendar $vCal parsed calendar object
|
||||
*/
|
||||
function processCalendarEventForTaskwarrior(VCalendar $vCal){
|
||||
try {
|
||||
$TWCalManager->buildCalendarEvent($VCal->VEVENT);
|
||||
$TWCalManager->buildToDoEvent($VCal->VTODO);
|
||||
} catch(Exception $e){
|
||||
throw new BadRequest($e->getMessage(), null, $e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* * This method is triggered whenever there was a calendar object gets
|
||||
* * created or updated.
|
||||
* *
|
||||
* * @param RequestInterface $request HTTP request
|
||||
* * @param ResponseInterface $response HTTP Response
|
||||
* * @param VCalendar $vCal Parsed iCalendar object
|
||||
* * @param mixed $calendarPath Path to calendar collection
|
||||
* * @param mixed $modified The iCalendar object has been touched.
|
||||
* * @param mixed $isNew Whether this was a new item or we're updating one
|
||||
* * @return void
|
||||
* */
|
||||
function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
|
||||
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
|
||||
$addresses = $this->getAddressesForPrincipal(
|
||||
$calendarNode->getOwner()
|
||||
);
|
||||
if ($isNew) {
|
||||
try {
|
||||
processCalendarEventForTaskwarrior($vCal);
|
||||
} catch(Exception\BadRequest $e){
|
||||
$response->setStatus(200);
|
||||
$response->setBody('');
|
||||
$response->setHeader('Content-Type', 'text/plain');
|
||||
$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This is the default implementation for the GET method.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpGet(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
if (!$node instanceof IFile) return;
|
||||
|
||||
$body = $node->get();
|
||||
|
||||
echo($body);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP OPTIONS
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpOptions(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$methods = $this->server->getAllowedMethods($request->getPath());
|
||||
echo('httpOptions ' . $response);
|
||||
|
||||
$response->setHeader('Allow', strtoupper(implode(', ', $methods)));
|
||||
$features = ['1', '3', 'extended-mkcol'];
|
||||
|
||||
foreach ($this->server->getPlugins() as $plugin) {
|
||||
$features = array_merge($features, $plugin->getFeatures());
|
||||
}
|
||||
|
||||
$response->setHeader('DAV', implode(', ', $features));
|
||||
$response->setHeader('MS-Author-Via', 'DAV');
|
||||
$response->setHeader('Accept-Ranges', 'bytes');
|
||||
$response->setHeader('Content-Length', '0');
|
||||
$response->setStatus(200);
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP HEAD
|
||||
*
|
||||
* This method is normally used to take a peak at a url, and only get the
|
||||
* HTTP response headers, without the body. This is used by clients to
|
||||
* determine if a remote file was changed, so they can use a local cached
|
||||
* version, instead of downloading it again
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpHead(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
// This is implemented by changing the HEAD request to a GET request,
|
||||
// and dropping the response body.
|
||||
$subRequest = clone $request;
|
||||
$subRequest->setMethod('GET');
|
||||
|
||||
try {
|
||||
$this->server->invokeMethod($subRequest, $response, false);
|
||||
$response->setBody('');
|
||||
} catch (Exception\NotImplemented $e) {
|
||||
// Some clients may do HEAD requests on collections, however, GET
|
||||
// requests and HEAD requests _may_ not be defined on a collection,
|
||||
// which would trigger a 501.
|
||||
// This breaks some clients though, so we're transforming these
|
||||
// 501s into 200s.
|
||||
$response->setStatus(200);
|
||||
$response->setBody('');
|
||||
$response->setHeader('Content-Type', 'text/plain');
|
||||
$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
|
||||
}
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Delete
|
||||
*
|
||||
* The HTTP delete method, deletes a given uri
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return void
|
||||
*/
|
||||
function httpDelete(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
echo('httpDelete.path ' . $path);
|
||||
|
||||
if (!$this->server->emit('beforeUnbind', [$path])) return false;
|
||||
$this->server->tree->delete($path);
|
||||
$this->server->emit('afterUnbind', [$path]);
|
||||
|
||||
$response->setStatus(204);
|
||||
$response->setHeader('Content-Length', '0');
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDAV PROPFIND
|
||||
*
|
||||
* This WebDAV method requests information about an uri resource, or a list of resources
|
||||
* If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
|
||||
* If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
|
||||
*
|
||||
* The request body contains an XML data structure that has a list of properties the client understands
|
||||
* The response body is also an xml document, containing information about every uri resource and the requested properties
|
||||
*
|
||||
* It has to return a HTTP 207 Multi-status status code
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return void
|
||||
*/
|
||||
function httpPropFind(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$requestBody = $request->getBodyAsString();
|
||||
if (strlen($requestBody)) {
|
||||
try {
|
||||
$propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
|
||||
} catch (ParseException $e) {
|
||||
throw new BadRequest($e->getMessage(), null, $e);
|
||||
}
|
||||
} else {
|
||||
$propFindXml = new Xml\Request\PropFind();
|
||||
$propFindXml->allProp = true;
|
||||
$propFindXml->properties = [];
|
||||
}
|
||||
|
||||
$depth = $this->server->getHTTPDepth(1);
|
||||
// The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
|
||||
if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
|
||||
|
||||
$newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth);
|
||||
|
||||
// This is a multi-status response
|
||||
$response->setStatus(207);
|
||||
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
|
||||
$response->setHeader('Vary', 'Brief,Prefer');
|
||||
|
||||
// Normally this header is only needed for OPTIONS responses, however..
|
||||
// iCal seems to also depend on these being set for PROPFIND. Since
|
||||
// this is not harmful, we'll add it.
|
||||
$features = ['1', '3', 'extended-mkcol'];
|
||||
foreach ($this->server->getPlugins() as $plugin) {
|
||||
$features = array_merge($features, $plugin->getFeatures());
|
||||
}
|
||||
$response->setHeader('DAV', implode(', ', $features));
|
||||
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
$minimal = $prefer['return'] === 'minimal';
|
||||
|
||||
$data = $this->server->generateMultiStatus($newProperties, $minimal);
|
||||
echo('httppropfind ' . $data);
|
||||
|
||||
$response->setBody($data);
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDAV PROPPATCH
|
||||
*
|
||||
* This method is called to update properties on a Node. The request is an XML body with all the mutations.
|
||||
* In this XML body it is specified which properties should be set/updated and/or deleted
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpPropPatch(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
try {
|
||||
$propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
|
||||
} catch (ParseException $e) {
|
||||
throw new BadRequest($e->getMessage(), null, $e);
|
||||
}
|
||||
$newProperties = $propPatch->properties;
|
||||
|
||||
$result = $this->server->updateProperties($path, $newProperties);
|
||||
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
$response->setHeader('Vary', 'Brief,Prefer');
|
||||
|
||||
if ($prefer['return'] === 'minimal') {
|
||||
|
||||
// If return-minimal is specified, we only have to check if the
|
||||
// request was succesful, and don't need to return the
|
||||
// multi-status.
|
||||
$ok = true;
|
||||
foreach ($result as $prop => $code) {
|
||||
if ((int)$code > 299) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
|
||||
$response->setStatus(204);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$response->setStatus(207);
|
||||
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
|
||||
|
||||
|
||||
// Reorganizing the result for generateMultiStatus
|
||||
$multiStatus = [];
|
||||
foreach ($result as $propertyName => $code) {
|
||||
if (isset($multiStatus[$code])) {
|
||||
$multiStatus[$code][$propertyName] = null;
|
||||
} else {
|
||||
$multiStatus[$code] = [$propertyName => null];
|
||||
}
|
||||
}
|
||||
$multiStatus['href'] = $path;
|
||||
|
||||
$response->setBody(
|
||||
$this->server->generateMultiStatus([$multiStatus])
|
||||
);
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP PUT method
|
||||
*
|
||||
* This HTTP method updates a file, or creates a new one.
|
||||
*
|
||||
* If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpPut(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$body = $request->getBodyAsStream();
|
||||
$path = $request->getPath();
|
||||
|
||||
// Intercepting Content-Range
|
||||
if ($request->getHeader('Content-Range')) {
|
||||
/*
|
||||
An origin server that allows PUT on a given target resource MUST send
|
||||
a 400 (Bad Request) response to a PUT request that contains a
|
||||
Content-Range header field.
|
||||
|
||||
Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4
|
||||
*/
|
||||
throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
|
||||
}
|
||||
|
||||
// Intercepting the Finder problem
|
||||
if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {
|
||||
|
||||
/*
|
||||
Many webservers will not cooperate well with Finder PUT requests,
|
||||
because it uses 'Chunked' transfer encoding for the request body.
|
||||
|
||||
The symptom of this problem is that Finder sends files to the
|
||||
server, but they arrive as 0-length files in PHP.
|
||||
|
||||
If we don't do anything, the user might think they are uploading
|
||||
files successfully, but they end up empty on the server. Instead,
|
||||
we throw back an error if we detect this.
|
||||
|
||||
The reason Finder uses Chunked, is because it thinks the files
|
||||
might change as it's being uploaded, and therefore the
|
||||
Content-Length can vary.
|
||||
|
||||
Instead it sends the X-Expected-Entity-Length header with the size
|
||||
of the file at the very start of the request. If this header is set,
|
||||
but we don't get a request body we will fail the request to
|
||||
protect the end-user.
|
||||
*/
|
||||
|
||||
// Only reading first byte
|
||||
$firstByte = fread($body, 1);
|
||||
if (strlen($firstByte) !== 1) {
|
||||
throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
|
||||
}
|
||||
|
||||
// The body needs to stay intact, so we copy everything to a
|
||||
// temporary stream.
|
||||
|
||||
$newBody = fopen('php://temp', 'r+');
|
||||
fwrite($newBody, $firstByte);
|
||||
stream_copy_to_stream($body, $newBody);
|
||||
rewind($newBody);
|
||||
|
||||
$body = $newBody;
|
||||
|
||||
}
|
||||
|
||||
if ($this->server->tree->nodeExists($path)) {
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
// If the node is a collection, we'll deny it
|
||||
if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.');
|
||||
|
||||
if (!$this->server->updateFile($path, $body, $etag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response->setHeader('Content-Length', '0');
|
||||
if ($etag) $response->setHeader('ETag', $etag);
|
||||
$response->setStatus(204);
|
||||
|
||||
} else {
|
||||
|
||||
$etag = null;
|
||||
// If we got here, the resource didn't exist yet.
|
||||
if (!$this->server->createFile($path, $body, $etag)) {
|
||||
// For one reason or another the file was not created.
|
||||
return false;
|
||||
}
|
||||
|
||||
$response->setHeader('Content-Length', '0');
|
||||
if ($etag) $response->setHeader('ETag', $etag);
|
||||
$response->setStatus(201);
|
||||
|
||||
}
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WebDAV MKCOL
|
||||
*
|
||||
* The MKCOL method is used to create a new collection (directory) on the server
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpMkcol(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$requestBody = $request->getBodyAsString();
|
||||
$path = $request->getPath();
|
||||
|
||||
if ($requestBody) {
|
||||
|
||||
$contentType = $request->getHeader('Content-Type');
|
||||
if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {
|
||||
|
||||
// We must throw 415 for unsupported mkcol bodies
|
||||
throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
$mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
|
||||
} catch (\Sabre\Xml\ParseException $e) {
|
||||
throw new Exception\BadRequest($e->getMessage(), null, $e);
|
||||
}
|
||||
|
||||
$properties = $mkcol->getProperties();
|
||||
|
||||
if (!isset($properties['{DAV:}resourcetype']))
|
||||
throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
|
||||
|
||||
$resourceType = $properties['{DAV:}resourcetype']->getValue();
|
||||
unset($properties['{DAV:}resourcetype']);
|
||||
|
||||
} else {
|
||||
|
||||
$properties = [];
|
||||
$resourceType = ['{DAV:}collection'];
|
||||
|
||||
}
|
||||
|
||||
$mkcol = new MkCol($resourceType, $properties);
|
||||
|
||||
$result = $this->server->createCollection($path, $mkcol);
|
||||
|
||||
if (is_array($result)) {
|
||||
$response->setStatus(207);
|
||||
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
|
||||
|
||||
$response->setBody(
|
||||
$this->server->generateMultiStatus([$result])
|
||||
);
|
||||
|
||||
} else {
|
||||
$response->setHeader('Content-Length', '0');
|
||||
$response->setStatus(201);
|
||||
}
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDAV HTTP MOVE method
|
||||
*
|
||||
* This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpMove(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$moveInfo = $this->server->getCopyAndMoveInfo($request);
|
||||
|
||||
if ($moveInfo['destinationExists']) {
|
||||
|
||||
if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false;
|
||||
|
||||
}
|
||||
if (!$this->server->emit('beforeUnbind', [$path])) return false;
|
||||
if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false;
|
||||
if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false;
|
||||
|
||||
if ($moveInfo['destinationExists']) {
|
||||
|
||||
$this->server->tree->delete($moveInfo['destination']);
|
||||
$this->server->emit('afterUnbind', [$moveInfo['destination']]);
|
||||
|
||||
}
|
||||
|
||||
$this->server->tree->move($path, $moveInfo['destination']);
|
||||
|
||||
// Its important afterMove is called before afterUnbind, because it
|
||||
// allows systems to transfer data from one path to another.
|
||||
// PropertyStorage uses this. If afterUnbind was first, it would clean
|
||||
// up all the properties before it has a chance.
|
||||
$this->server->emit('afterMove', [$path, $moveInfo['destination']]);
|
||||
$this->server->emit('afterUnbind', [$path]);
|
||||
$this->server->emit('afterBind', [$moveInfo['destination']]);
|
||||
|
||||
// If a resource was overwritten we should send a 204, otherwise a 201
|
||||
$response->setHeader('Content-Length', '0');
|
||||
$response->setStatus($moveInfo['destinationExists'] ? 204 : 201);
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDAV HTTP COPY method
|
||||
*
|
||||
* This method copies one uri to a different uri, and works much like the MOVE request
|
||||
* A lot of the actual request processing is done in getCopyMoveInfo
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpCopy(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$copyInfo = $this->server->getCopyAndMoveInfo($request);
|
||||
|
||||
if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;
|
||||
if ($copyInfo['destinationExists']) {
|
||||
if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false;
|
||||
$this->server->tree->delete($copyInfo['destination']);
|
||||
}
|
||||
|
||||
$this->server->tree->copy($path, $copyInfo['destination']);
|
||||
$this->server->emit('afterBind', [$copyInfo['destination']]);
|
||||
|
||||
// If a resource was overwritten we should send a 204, otherwise a 201
|
||||
$response->setHeader('Content-Length', '0');
|
||||
$response->setStatus($copyInfo['destinationExists'] ? 204 : 201);
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP REPORT method implementation
|
||||
*
|
||||
* Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
|
||||
* It's used in a lot of extensions, so it made sense to implement it into the core.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
function httpReport(RequestInterface $request, ResponseInterface $response) {
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$result = $this->server->xml->parse(
|
||||
$request->getBody(),
|
||||
$request->getUrl(),
|
||||
$rootElementName
|
||||
);
|
||||
|
||||
if ($this->server->emit('report', [$rootElementName, $result, $path])) {
|
||||
|
||||
// If emit returned true, it means the report was not supported
|
||||
throw new Exception\ReportNotSupported();
|
||||
|
||||
}
|
||||
|
||||
// Sending back false will interupt the event chain and tell the server
|
||||
// we've handled this method.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called during property updates.
|
||||
*
|
||||
* Here we check if a user attempted to update a protected property and
|
||||
* ensure that the process fails if this is the case.
|
||||
*
|
||||
* @param string $path
|
||||
* @param PropPatch $propPatch
|
||||
* @return void
|
||||
*/
|
||||
function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) {
|
||||
|
||||
// Comparing the mutation list to the list of propetected properties.
|
||||
$mutations = $propPatch->getMutations();
|
||||
|
||||
$protected = array_intersect(
|
||||
$this->server->protectedProperties,
|
||||
array_keys($mutations)
|
||||
);
|
||||
|
||||
if ($protected) {
|
||||
$propPatch->setResultCode($protected, 403);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called during property updates.
|
||||
*
|
||||
* Here we check if a node implements IProperties and let the node handle
|
||||
* updating of (some) properties.
|
||||
*
|
||||
* @param string $path
|
||||
* @param PropPatch $propPatch
|
||||
* @return void
|
||||
*/
|
||||
function propPatchNodeUpdate($path, PropPatch $propPatch) {
|
||||
|
||||
// This should trigger a 404 if the node doesn't exist.
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
if ($node instanceof IProperties) {
|
||||
$node->propPatch($propPatch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when properties are retrieved.
|
||||
*
|
||||
* Here we add all the default properties.
|
||||
*
|
||||
* @param PropFind $propFind
|
||||
* @param INode $node
|
||||
* @return void
|
||||
*/
|
||||
function propFind(PropFind $propFind, INode $node) {
|
||||
|
||||
$propFind->handle('{DAV:}getlastmodified', function() use ($node) {
|
||||
$lm = $node->getLastModified();
|
||||
if ($lm) {
|
||||
return new Xml\Property\GetLastModified($lm);
|
||||
}
|
||||
});
|
||||
|
||||
if ($node instanceof IFile) {
|
||||
$propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
|
||||
$propFind->handle('{DAV:}getetag', [$node, 'getETag']);
|
||||
$propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
|
||||
}
|
||||
|
||||
if ($node instanceof IQuota) {
|
||||
$quotaInfo = null;
|
||||
$propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) {
|
||||
$quotaInfo = $node->getQuotaInfo();
|
||||
return $quotaInfo[0];
|
||||
});
|
||||
$propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) {
|
||||
if (!$quotaInfo) {
|
||||
$quotaInfo = $node->getQuotaInfo();
|
||||
}
|
||||
return $quotaInfo[1];
|
||||
});
|
||||
}
|
||||
|
||||
$propFind->handle('{DAV:}supported-report-set', function() use ($propFind) {
|
||||
$reports = [];
|
||||
foreach ($this->server->getPlugins() as $plugin) {
|
||||
$reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
|
||||
}
|
||||
return new Xml\Property\SupportedReportSet($reports);
|
||||
});
|
||||
$propFind->handle('{DAV:}resourcetype', function() use ($node) {
|
||||
return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
|
||||
});
|
||||
$propFind->handle('{DAV:}supported-method-set', function() use ($propFind) {
|
||||
return new Xml\Property\SupportedMethodSet(
|
||||
$this->server->getAllowedMethods($propFind->getPath())
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches properties for a node.
|
||||
*
|
||||
* This event is called a bit later, so plugins have a chance first to
|
||||
* populate the result.
|
||||
*
|
||||
* @param PropFind $propFind
|
||||
* @param INode $node
|
||||
* @return void
|
||||
*/
|
||||
function propFindNode(PropFind $propFind, INode $node) {
|
||||
|
||||
if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
|
||||
|
||||
$nodeProperties = $node->getProperties($propertyNames);
|
||||
foreach ($propertyNames as $propertyName) {
|
||||
if (array_key_exists($propertyName, $nodeProperties)) {
|
||||
$propFind->set($propertyName, $nodeProperties[$propertyName], 200);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when properties are retrieved.
|
||||
*
|
||||
* This specific handler is called very late in the process, because we
|
||||
* want other systems to first have a chance to handle the properties.
|
||||
*
|
||||
* @param PropFind $propFind
|
||||
* @param INode $node
|
||||
* @return void
|
||||
*/
|
||||
function propFindLate(PropFind $propFind, INode $node) {
|
||||
|
||||
$propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) {
|
||||
|
||||
// If we already have a sync-token from the current propFind
|
||||
// request, we can re-use that.
|
||||
$val = $propFind->get('{http://sabredav.org/ns}sync-token');
|
||||
if ($val) return $val;
|
||||
|
||||
$val = $propFind->get('{DAV:}sync-token');
|
||||
if ($val && is_scalar($val)) {
|
||||
return $val;
|
||||
}
|
||||
if ($val && $val instanceof Xml\Property\Href) {
|
||||
return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
|
||||
}
|
||||
|
||||
// If we got here, the earlier two properties may simply not have
|
||||
// been part of the earlier request. We're going to fetch them.
|
||||
$result = $this->server->getProperties($propFind->getPath(), [
|
||||
'{http://sabredav.org/ns}sync-token',
|
||||
'{DAV:}sync-token',
|
||||
]);
|
||||
|
||||
if (isset($result['{http://sabredav.org/ns}sync-token'])) {
|
||||
return $result['{http://sabredav.org/ns}sync-token'];
|
||||
}
|
||||
if (isset($result['{DAV:}sync-token'])) {
|
||||
$val = $result['{DAV:}sync-token'];
|
||||
if (is_scalar($val)) {
|
||||
return $val;
|
||||
} elseif ($val instanceof Xml\Property\Href) {
|
||||
return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => 'The Core plugin provides syncronization between tasks and iCAL events',
|
||||
'link' => null,
|
||||
];
|
||||
|
||||
}
|
||||
}
|
86
libs/TaskwarriorCalendarEvent.php
Normal file
86
libs/TaskwarriorCalendarEvent.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Aerex\Taskwarrior;
|
||||
|
||||
use Aerex\Taskwarrior\Config;
|
||||
use DavidBadura\Taskwarrior\Taskwarrior;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
|
||||
class TaskwarriorCalendarEvent {
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
private $taskrc;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
private $taskDataDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
private $taskBinFile;
|
||||
|
||||
|
||||
/**
|
||||
* @var Taskwarrior
|
||||
*/
|
||||
|
||||
private $taskwarrior;
|
||||
|
||||
|
||||
public function __construct(){
|
||||
$this->config = new Config();
|
||||
|
||||
if($this->config.isNotValidConfiguration()){
|
||||
$invalidConfigurationString = $this->config.invalidConfigurations();
|
||||
|
||||
$invalidConfigurationMessage = sprintf('The following configurations are invalid %s and' .
|
||||
' the default configurations will be used', $invalidConfigurationString);
|
||||
echo($invalidConfigurationMessage);
|
||||
|
||||
$this->config.setDefaults();
|
||||
}
|
||||
|
||||
$this->taskrc = $this->config->getTaskRC();
|
||||
$this->taskDataDir = $this->config->getTaskDataDir();
|
||||
$this->taskBinFile = $this->config->getTaskBinFile();
|
||||
|
||||
$this->taskwarrior = $taskwarrior($this->taskrc,$this->taskDataDir, [], $this->taskBinFile);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function buildCalendarEvent(VCalendar $vEvent){
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function buildToDoEvent(VCalendar $vToDo){
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
40
test/PluginTest.php
Normal file
40
test/PluginTest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Sabre\VObject;
|
||||
use Aerex\Taskwarrior;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
|
||||
|
||||
class PluginTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
protected $cal;
|
||||
protected $pluginInstance;
|
||||
|
||||
function setup(){
|
||||
$this->$cal = new Componet\VCalendar();
|
||||
$pluginInstance = new Plugin();
|
||||
|
||||
}
|
||||
|
||||
function testCreateSimpleTask() {
|
||||
$expectedTaskDescription = "Going to the Movies";
|
||||
$expectedStartTime = new \DateTime("2018-03-13 09:33:00Z");
|
||||
$expectedEndTime = new \DateTime("2018-03-13 10:45:00Z");
|
||||
|
||||
$vCalendarObjectEvent = $this->$cal->add('VEVENT', [
|
||||
"UID" => "1ff0313e-1ffa-4a18-b8c1-449bddc9109c",
|
||||
"SUMMARY" => $expectedTaskDescription,
|
||||
"DTSTART" => $expectedStartTime,
|
||||
"DTEND" => $expectedEndTime
|
||||
], false);
|
||||
|
||||
|
||||
$this->$pluginInstance->processCalendarEventForTaskwarrior($vCalendarObjectEvent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
Loading…
Reference in New Issue
Block a user