Compare commits

..

11 Commits
master ... v2

Author SHA1 Message Date
Aerex 32d28be931 feat: Updated sabre/dav to version 4 2020-03-03 22:40:12 -06:00
Aerex 06e67dec3c fix: Used correct namespace
- fix: Removed <? at end of file
2020-02-26 01:37:23 -06:00
Aerex 75ccc2d316 feat: Updated sabre/dev dependency to 3.2.3
- feat: Updated nestbot/carbon to 2.0.0
- fix: Replaced zendframework/zend-validator with
  laminas/laminas-validator
- fix: Replaced zendframework/zend-stdlib with
  laminas/laminas-stdlib
2020-02-25 23:02:06 -06:00
Aerex 6657fab031 refactor: rename Taskwarrior folder TW 2018-12-01 23:07:36 -06:00
Aerex 488cf14aac fix: remove jms serialize dependency 2018-12-01 21:11:56 -06:00
Aerex f713fbb16b feat: added modify taskwarrior command 2018-12-01 20:55:56 -06:00
Aerex 68470e3dee style: remove unused code and updated plugin 2018-11-26 00:58:20 -06:00
Aerex a2b2073bad fix: syntax config 2018-11-25 19:21:31 -06:00
Aerex 4d96cc4e8c fix: change namespaces 2018-11-25 18:41:20 -06:00
Aerex 98b67855ca refactor: clean up code and remove some dependencies 2018-11-25 17:35:33 -06:00
Aerex 2d2122e05d refactor: extract ical processor code into an interface type 2018-11-05 00:18:47 -06:00
17 changed files with 1507 additions and 2956 deletions

View File

@ -9,16 +9,21 @@
"Baikal", "Baikal",
"sabre" "sabre"
], ],
"repositories": [
"repositories": [{"type": "vcs", "url": "https://aerex.me/git/Aerex/Taskwarrior"}], {
"type": "vcs",
"url": "https://git.aerex.me/Aerex/Taskwarrior"
}
],
"require": { "require": {
"php": ">=5.5", "php": ">=5.5",
"sabre/dav": "~3.1.2", "sabre/dav" : "~4.0.2",
"jms/serializer": "2.0.0-RC1",
"jms/metadata": "^2.0@RC",
"aerex/taskwarrior": "^3.0",
"sabre/vobject": "^4.0", "sabre/vobject": "^4.0",
"easycorp/easy-log-handler": "^1.0" "easycorp/easy-log-handler": "^1.0",
"nesbot/carbon": "^2.0.0",
"laminas/laminas-validator": "^2.13",
"laminas/laminas-stdlib": "^3.2",
"psr/container": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "> 4.8, <=6.0.0" "phpunit/phpunit": "> 4.8, <=6.0.0"

2310
composer.lock generated

File diff suppressed because it is too large Load Diff

43
src/CalendarProcessor.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace Aerex\TaskwarriorPlugin;
use Aerex\TaskwarriorPlugin\TW\TaskwarriorManager;
use Sabre\VObject\Component\VTodo;
/**
* Class CalendarProcessor
*
* @author Aerex
*/
class CalendarProcessor
{
public function __construct(TaskwarriorManager $taskwarriorManager){
$this->taskwarriorManager = $taskwarriorManager;
}
public function importTask(VTodo $Todo){
if($this->taskwarriorManager->taskExists($Todo->UID)){
$this->taskwarriorManager->updateTask($Todo);
}
try {
return $this->taskwarriorManager->addTask($Todo);
} catch(Exception $e){
echo $e->getMessage();
throw $e;
}
}
public function export(){
echo "Not yet implemented";
}
}
?>

View File

@ -2,8 +2,8 @@
namespace Aerex\TaskwarriorPlugin; namespace Aerex\TaskwarriorPlugin;
use DavidBadura\Taskwarrior\Taskwarrior; use Aerex\TaskwarriorPlugin\TW\Taskwarrior;
use Aerex\Taskwarrior\TaskwarriorManager; use Aerex\TaskwarriorPlugin\TW\TaskwarriorManager;
class Config { class Config {
@ -28,14 +28,34 @@ class Config {
private $rcOptions; private $rcOptions;
public function __construct($taskrc='~/.taskrc', $taskData='~/.task',$rcOptions = [], $bin='task'){ public function __construct($taskrc='~/.taskrc', $taskData='~/.task',$rcOptions = [], $bin='task'){
$this->taskrc = $taskrc; $this->taskrc = $taskrc;
$this->bin = $bin; $this->bin = $bin;
$this->taskDat = $taskData; $this->taskData = $taskData;
$this->rcOptions = $rcOptions; $this->rcOptions = array_merge(
array(
'rc:' . $this->taskrc,
'rc.data.location=' . $this->taskData,
'rc.json.array=true',
'rc.json.depends.array=true',
'rc.confirmation=no',
),
$rcOptions
);
} }
public function getTaskBin(){
return $this->bin;
}
public function getOptions(){
return $this->rcOptions;
}
public function getTaskwarriorInstance(){ public function getTaskwarriorInstance(){
$this->taskwarrior = new Taskwarrior($this->taskrc,$this->taskdatadir, [], $this->taskbinfile); $this->taskwarrior = new Taskwarrior($this->taskrc,$this->taskdatadir, [], $this->taskbinfile);

View File

@ -0,0 +1,7 @@
<?php
class TaskwarriorCommandException extends Exception {
}
?>

View File

@ -9,7 +9,9 @@ use Sabre\HTTP\ResponseInterface;
use Sabre\Xml\ParseException; use Sabre\Xml\ParseException;
use Sabre\DAV\ServerPlugin; use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Server; use Sabre\DAV\Server;
use Aerex\TaskwarriorPlugin\iCalEventProcessor; use Aerex\TaskwarriorPlugin\CalendarProcessor;
use Aerex\TaskwarriorPlugin\TW\Taskwarrior;
use Aerex\TaskwarriorPlugin\TW\TaskwarriorManager;
use Aerex\TaskwarriorPlugin\Config; use Aerex\TaskwarriorPlugin\Config;
/** /**
@ -26,30 +28,24 @@ class Plugin extends ServerPlugin {
protected $server; protected $server;
/**
* Reference to TaskwarriorConfig object
* @var TaskwarriorConfig
*
*/
protected $twConfig;
/** /**
* Reference to TaskwarriorCalenderEvent object * Reference to TaskwarriorCalenderEvent object
* @var TaskwarriorCalendarEvent * @var TaskwarriorManager
*/ */
protected $TWCalManager; protected $TWCalManager;
/** /**
* Creates the Taskwarrior plugin * Creates the Taskwarrior plugin
* *
* @param TaskwarriorConfig $TWCalManager * @param CalendarProcessor $TWCalManager
* *
*/ */
function __construct(iCalEventProcessor $TWCalManager = null){ function __construct(Taskwarrior $taskwarrior = null){
if(!is_null($TWCalManager)){ if(!is_null($taskwarrior)){
$this->twCalManager = $TWCalManager; $this->twCalManager = new CalendarProcessor(new TaskwarriorManager($taskwarrior));
} else { } else {
$this->twCalManager = new iCalEventProcessor(); $this->twCalManager = new CalendarProcessor(new TaskwarriorManager());
} }
} }
@ -61,23 +57,7 @@ class Plugin extends ServerPlugin {
function initialize(Server $server) { function initialize(Server $server) {
$this->server = $server; $this->server = $server;
$server->on('method:GET', [$this, 'httpGet']); $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
$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);
} }
@ -102,12 +82,16 @@ class Plugin extends ServerPlugin {
*/ */
function processCalendarEventForTaskwarrior(Document $vCal){ function processCalendarEventForTaskwarrior(Document $vCal){
try { try {
$this->twCalManager->importTask($vCal->VTODO); if(isset($vCal->VTODO)){
$response = $this->twCalManager->importTask($vCal->VTODO);
}
} catch(BadRequest $e){ } catch(BadRequest $e){
throw new BadRequest($e->getMessage(), null, $e); throw new BadRequest($e->getMessage(), null, $e);
} catch(Exception $e){ } catch(Exception $e){
throw new Exception($e->getMessage(), null, $e); throw new Exception($e->getMessage(), null, $e);
} }
return $response;
} }
/** /**
* * This method is triggered whenever there was a calendar object gets * * This method is triggered whenever there was a calendar object gets
@ -123,748 +107,18 @@ class Plugin extends ServerPlugin {
* */ * */
function calendarObjectChange(RequestInterface $request, ResponseInterface $response, Document $vCal, $calendarPath, &$modified, $isNew) { function calendarObjectChange(RequestInterface $request, ResponseInterface $response, Document $vCal, $calendarPath, &$modified, $isNew) {
$calendarNode = $this->server->tree->getNodeForPath($calendarPath); $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
$addresses = $this->getAddressesForPrincipal( $body = '';
$calendarNode->getOwner()
);
if ($isNew) { if ($isNew) {
try { try {
$this->processCalendarEventForTaskwarrior($vCal); $body = $this->processCalendarEventForTaskwarrior($vCal);
} catch(Exception\BadRequest $e){ } catch(Exception\BadRequest $e){
$response->setStatus(200);
$response->setBody('');
$response->setHeader('Content-Type', 'text/plain');
$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode()); $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->setStatus(200);
$response->setBody(''); $response->setBody('');
$response->setHeader('Content-Type', 'text/plain'); $response->setHeader('Content-Type', 'text/plain');
$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode()); return true;
}
// 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));
}
}
});
} }
/** /**

View File

@ -1,9 +0,0 @@
<?php
class TaskwarriorProps {
static $DESCRIPTION = 'description';
}
?>

View File

@ -0,0 +1,11 @@
<?php
namespace Aerex\TaskwarriorPlugin\TW\Commands;
use Aerex\TaskwarriorPlugin\TW\Task;
interface IStrategy {
public function add(Task $task);
public function count(Task $task);
}
?>

View File

@ -0,0 +1,115 @@
<?php
namespace Aerex\TaskwarriorPlugin\TW\Commands;
use Aerex\TaskwarriorPlugin\TW\Task;
use Aerex\TaskwarriorPlugin\Config;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Aerex\TaskwarriorPlugin\Exceptions\TaskwarriorCommandException;
class TodoStrategy implements IStrategy {
private $config;
public function __construct(Config $config){
$this->config = $config;
}
public function count($uuid){
$cmd[] = $this->config->getTaskBin();
$cmd[] = sprintf('%s count', $uuid);
return $this->executeCommand($cmd);
}
public function modify($task){
$cmd[] = $this->config->getTaskBin();
$uuid = $task->getUuid();
$taskJson = $task->convertToArray();
// Append modify command
$cmd[] = sprintf(' %s modify ', $uuid);
$document = $task->getDescription();
// Append as first modifier description if set
if(isset($document)){
$cmd[] = $document;
}
// Append on modifiers
foreach($taskJson as $prop => $value){
if(isset($value) && $value != null){
$cmd[] = sprintf(' %s: %s ', $prop, $value);
}
}
return $this->executeCommand($cmd);
}
public function add(Task $task){
$cmd[] = $this->config->getTaskBin();
$cmd[] = 'add';
if($task->getDescription() != null){
$cmd[] = sprintf('"%s"', (string)$task->getDescription());
}
if($task->getCategories() != null){
$categories = implode(' +', (string)$task->getCategories());
$cmd[] = $categories;
}
if($task->getDue() != null){
$cmd[] = sprintf("due:%s",$task->getDue('Y-m-dTH:i:s'));
}
if($task->getRecurrence() != null){
$rrule = $task->getRecurrence()->getParts();
$cmd[] = sprintf('recur:%s', $rrule['FREQ']);
if(isset($rrule['UNTIL'])){
$cmd[] = sprintf('until:%s', $rrule['UNTIL']);
}
}
if($task->getStatus() != null){
$cmd[] = sprintf('status:%s', (string)$task->getStatus());
}
return $this->executeCommand($cmd);
}
private function executeCommand($command){
$rcOptions = $this->config->getOptions();
foreach ($rcOptions as $rcOption) {
$command[] = $rcOption;
}
$cmdString = implode(' ', $command);
echo $cmdString;
$process = new Process($cmdString);
try {
$process->mustRun();
// clear cmd queue
$this->cmd = [];
return $process->getOutput();
} catch(ProcessFailedException $error){
throw new TaskwarriorCommandException($error->getMesage());
return;
}
}
}

297
src/TW/Task.php Normal file
View File

@ -0,0 +1,297 @@
<?php
namespace Aerex\TaskwarriorPlugin\TW;
use Laminas\Validator\Uuid;
use Carbon\Carbon;
use Sabre\VObject\Component\VTodo;
/**
* Task
* @author Aerex
*/
class Task {
/**
* @var string
*
* @JMS\Type("string")
*/
private $uuid;
/**
* @var string
*
* @JMS\Type("string")
*/
private $description;
/**
* @var string
*
*/
private $priority;
/**
* @var string
*
*/
private $project;
/**
* @var Carbon
*
*/
private $due;
/**
* @var Carbon
*
*/
private $wait;
/**
* @var array
*
*/
private $tags;
/**
* @var float
*
*/
private $urgency;
/**
* @var Carbon
*
*/
private $entry;
/**
* @var Carbon
*
*/
private $start;
/**
* @var string
*
*/
private $recur;
/**
* @var Carbon
*
*/
private $until;
/**
* @var Annotation[]
*
*/
private $annotations = [];
/**
* @var Carbon
*
*/
private $modified;
/**
* @var Carbon
*
*/
private $end;
/**
* @var string
*
*/
private $status;
/**
* @var Task[]|ArrayCollection
*
*/
private $depends;
public function __construct($UUID=null){
$validator = new Uuid();
if(!isset($UUID)){
$this->uuid = uniqid();
} else if(isset($UUID) && !$validator->isValid($UUID)){
throw new Exception(sprintf('%s is not a valid uuid', $UUID));
}
$this->uuid = $UUID;
}
public function getUuid(){
return $this->uuid;
}
public function convertToArray(){
$tagsString = null;
$dependsString = null;
$dueString = null;
// Process array properties
if(isset($this->tags)){
$tagsString = implode(',', $this->tags);
}
if(isset($this->depends)){
$dependsString = implode(',', $this->depends);
}
// Process date properties
if(isset($this->due)){
$dueString = $this->due->format('Y-m-dTH:i:s');
}
return array(
"status" => $this->status,
"start" => $this->start,
"wait" => $this->wait,
"end" => $this->end,
"entry" => $this->entry,
"priority" => $this->priority,
"project" => $this->project,
"due" => $this->due,
"tags" => $tagsString,
"urgency" => $this->urgency,
"recu" => $this->recur,
"until" => $this->until,
"tags" => $this->annotations,
"modified" => $this->modified,
"depends" => $dependsString
);
}
/**
*
*
* If description is not available attempt to summary, otherwise throw Exception
*/
public function setDescription(VTodo $component){
if(!isset($component->DESCRIPTION) && isset($component->SUMMARY)){
$this->description = $component->SUMMARY;
} else if(!isset($component->DESCRIPTION) && !isset($component->SUMMARY)){
throw new Exception("Task must have a description or summary");
} else {
$this->description = $component->DESCRIPTION;
}
}
public function getDescription(){
return $this->description;
}
public function setEntryTime(VTodo $document){
if(isset($document->DTSTAMP)){
$this->entry = new Carbon($document->DTSTAMP->getDateTime()->format(\DateTime::W3C));
} else {
throw new Exception('Task must have a entry time');
}
}
public function getEntryTime(){
return $this->entry;
}
public function setStartTime(VTodo $document){
if(isset($document->DTSTART)){
$this->start = new Carbon($document->DTSTART->getDateTime()->format(\DateTime::W3C));
}
}
public function getStartTime(){
return $this->start;
}
public function setModifiedTime(VTodo $document){
if(isset($document->{'LAST-MODIFIED'})){
$this->modified = new Carbon($document->{'LAST-MODIFIED'}->getDateTime()->format(\DateTime::W3C));
}
}
public function getModifiedTime(){
return $this->modified;
}
public function setDue(VTodo $document){
if(isset($document->DUE)){
$this->due = new Carbon($document->DUE->getDateTime()->format(\DateTime::W3C));
}
}
public function getDue($format=null){
if($format != null){
return $this->due->format($format);
}
return $this->due;
}
public function setStopTime(VTodo $document){
if(isset($document->DTEND)){
$this->end = new Carbon($document->DTEND->getDateTime()->format(\DateTime::W3C));
}
}
public function getStopTime(){
return $this->end;
}
public function setCategories(VTodo $document){
if(isset($document->CATEGORIES)){
$this->tags = explode(',', (string)$document->CATEGORIES);
}
}
public function getCategories(){
return $this->tags;
}
public function setStatus(VTodo $document){
if(isset($document->STATUS)){
switch((string)$document->STATUS){
case 'NEEDS-ACTION':
$this->status = 'pending';
break;
case 'COMPLETED':
$this->status = 'completed';
break;
case 'CANCELED':
$this->status = 'deleted';
break;
}
}
}
public function getRecurrence(){
return $this->recur;
}
public function setRecurrence(VTodo $document){
if(isset($document->RRULE)){
$this->recur = $document->RRULE;
}
}
public function getStatus(){
return $this->status;
}
}

84
src/TW/Taskwarrior.php Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace Aerex\TaskwarriorPlugin\TW;
use Symfony\Component\Process\Process;
use Aerex\TaskwarriorPlugin\TW\Commands\IStrategy;
use Aerex\TaskwarriorPlugin\Config;
class Taskwarrior {
/**
* @var IStrategy
*/
private $strategy;
/**
* @var TaskwarriorConfig
*/
private $config;
/**
* @var string
*/
private $bin;
/**
* @var string
*/
private $taskrc;
/**
* @var string
*/
private $taskData;
/**
* @var array
*/
private $rcOptions;
/**
* @var string
*/
public function __construct(Config $config){
if(!isset($config)){
$this->config = new Config();
}
$this->config = $config;
}
public function getConfig(){
return $this->config;
}
public function setStrategy($strategy){
$this->strategy = $strategy;
}
public function createTask($uuid){
$task = new Task($uuid);
return $task;
}
public function add($task){
$this->strategy->add($task);
}
public function count($uuid){
return $this->strategy->count($uuid);
}
public function modify($task){
return $this->strategy->modify($task);
}
}
?>

View File

@ -0,0 +1,95 @@
<?php
namespace Aerex\TaskwarriorPlugin\TW;
use Aerex\TaskwarriorPlugin\TW\Taskwarrior;
use Aerex\TaskwarriorPlugin\TW\Task;
use Aerex\TaskwarriorPlugin\TW\Commands\TodoStrategy;
use Sabre\VObject\Component\VTodo;
class TaskwarriorManager {
/**
* @var TodoStrategy
*/
private $todoStrategy;
public function __construct($taskwarrior){
if(!isset($taskwarrior)){
$this->taskwarrior = new Taskwarrior();
} else {
$this->taskwarrior = $taskwarrior;
}
// Initialize strategies
$this->todoStrategy = new TodoStrategy($this->taskwarrior->getConfig());
}
public function addTask(VTodo $document){
$this->taskwarrior->setStrategy($this->todoStrategy);
$task = $this->taskwarrior->createTask((string)$document->UID);
$task->setDescription($document);
$task->setEntryTime($document);
$task->setDue($document);
$task->setCategories($document);
$task->setRecurrence($document);
return $this->taskwarrior->add($task);
}
public function updateTask(VTodo $document){
$task = $this->taskwarrior->createTask((string)$document->UID);
$task->setDescription($document);
$task->setEntryTime($document);
$task->setStartTime($document);
$task->setModifiedTime($document);
$task->setStopTime($document);
$task->setDue($document);
$task->setCategories($document);
$task->setStatus($document);
$task->setRecurrence($document);
$updatedTask = $this->taskwarrior->modify($task);
return $updatedTask;
}
public function taskExists($taskUuid){
$this->taskwarrior->setStrategy($this->todoStrategy);
$exists = $this->taskwarrior->count((string)$taskUuid);
echo("exists " . $exists);
if($exists === "1"){
return true;
}
return false;
}
}
?>

View File

@ -1,112 +0,0 @@
<?php
namespace Aerex\TaskwarriorPlugin;
use Aerex\TaskwarriorPlugin\Taskwarrior;
use DavidBadura\Taskwarrior\TaskManager;
use DavidBadura\Taskwarrior\Task;
class TaskwarriorManager extends TaskManager {
const DESCRIPTION = 'description';
const CATEGORIES = 'categories';
const TASK_UUID = 'uuid';
const ICAL_UID = 'uid';
/**
*
* @var \DavidBadura\Taskwarrior\Task
*/
private $tasks;
const ENTRY = 'entry';
const START = 'start';
const MODIFIED = 'modified';
const END = 'end';
public function __construct($taskwarrior){
parent::__construct($taskwarrior);
}
public function createTask($UUID){
$task = new Task();
$this->setValue($task, $ICAL_UID, $UUID);
return $task;
}
public function export(){
}
function setEntryTime($entry){
}
function exists($UUID){}
function setEndTime($end){}
function setModifiedTime($modifiedTime){}
function setSummary($description){
if(!isset($description)){
return;
}
$this->taskWarriorJSON[self::DESCRIPTION] = $description;
}
function setStartTime($startTime){}
function setCategories($categories){
if(!isset($categories)){
return;
}
if(!is_array($catergories)){
return;
}
$this->taskWarriorJSON[self::CATEGORIES] = $categories;
}
public function taskExists($taskUuid){
$taskIsInCache = isset($this->cachedTasks[$taskUuid]);
if($taskIsInCache){
return true;
}
$jsonArray = $this->taskwarrior->export($taskUuid);
$taskWithUuidExists = count($jsonArray) > 0;
return $taskWithUuidExists;
}
public function save(){
}
public function build(){
}
/**
* @param Task $task
* @param string $attr
* @param mixed $value
*/
public function setValue(Task $task, $attr, $value)
{
$refClass = new \ReflectionClass(Task::class);
$refProp = $refClass->getProperty($attr);
$refProp->setAccessible(true);
$refProp->setValue($task, $value);
}
function setDescription($description){}
function parseiCalDateTime($iCalDateTime){}
function convertToStringArray($categories){}
function setDueDate($dueDate){}
}
?>

View File

@ -1,130 +0,0 @@
<?php
namespace Aerex\TaskwarriorPlugin;
use Aerex\TaskwarriorPlugin\Config;
use Sabre\DAV\Exception;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Component\VTodo;
class iCalEventProcessor {
/**
* @var Config
*/
private $config;
/**
* @var string
*
*/
private $taskrc;
/**
* @var string
*/
private $taskDataDir;
/**
* @var array
*/
private $cachedTasks = [];
/**
* @var string
*/
private $taskBinFile;
/**
* @var Taskwarrior
*/
private $taskwarrior;
public function __construct(Config $taskConfig = null){
if(!is_null($taskConfig)){
$this->taskConfig = $taskConfig;
} else {
$this->taskConfig = new Config();
}
$this->taskwarrior = $this->taskConfig->getTaskwarriorInstance();
$this->logger = $this->taskConfig->getLogger();
}
public function importTask(VTodo $ToDoComponent = null){
if(!isset($ToDoComponent)){
$this->logger->error("vCal ToDo component is not defined");
throw new Exception("vCal Todo component is not defined");
}
if($this->taskwarrior->exists($ToDoComponent->UID)){
$this->logger->error("Event already exists " . (string)$ToDoComponent->UID);
}
try {
// parse iCalendar event times to DateTime objects
//$entry = $this->taskwarrior->parseiCalDateTime($ToDoComponent->DSTAMP);
//$start = $this->taskwarrior->parseiCalDateTime($ToDoComponent->DSTART);
//$modified = $this->taskwarrior->parseiCalDateTime($ToDoComponent->{'LAST-MODIFIED'});
//$end = $this->taskwarrior->parseiCalDateTime($ToDoComponent->DTEND);
$start = $ToDoComponent->DSTART;
$entry = $ToDoComponent->DSTAMP;
$modified = $ToDoComponent->{'LAST-MODIFIED'};
$end = $ToDoComponent->DTEND;
$task = $this->taskwarrior->createTask($ToDoComponent->UID);
$tags = $this->taskwarrior->convertToStringArray($ToDoComponent->CATEGORIES);
$task->setTags($tags);
$task->setDue($ToDoComponent->DUE->getDateTime());
$task->setDescription($ToDoComponent->DESCRIPTION);
// override protected taskwarrior properties using iCal event
$this->taskwarrior->setValue($task, $this->taskwarrior::ENTRY, $entry);
$this->taskwarrior->setValue($task, $this->taskwarrior::START, $start);
$this->taskwarrior->setValue($task, $this->taskwarrior::MODIFIED, $modified);
$this->taskwarrior->setValue($task, $this->taskwarrior::END, $end);
$this->taskwarrior->save($task);
} catch(Exception $e){
$this->logger->error($e->message);
throw $e;
}
}
}
?>

View File

@ -0,0 +1,59 @@
<?php
use Sabre\DAV\Exception\BadRequest;
use Monolog\Logger;
use Aerex\TaskwarriorPlugin\TaskwarriorManager;
use Aerex\TaskwarriorPlugin\Processors\ToDo;
use Sabre\VObject\Component\VCalendar;
use DavidBadura\Taskwarrior\Task;
use DateTime;
class TodoTest extends \PHPUnit\Framework\TestCase {
/**
* @var \PHPUnit_Framework_MockObject_MockObject
* */
private $mockTaskwarriorManager;
function setup(){
$this->mockTaskwarriorManager = $this->createMock(TaskwarriorManager::class);
}
function testImportAndAddTask(){
$uuid = '9f353281-1051-4c45-92db-462f5d353c76';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$this->mockTaskwarriorManager->expects($this->once())->method('exists')->with($this->equalTo($uuid))
->willReturn(false);
$this->mockTaskwarriorManager->expects($this->once())->method('addTask')->with($this->equalTo($mockVTodo));
$todo = new ToDo($this->mockTaskwarriorManager);
$todo->import($mockVTodo);
}
function testImportAndUpdateTask(){
$uuid = '9f353281-1051-4c45-92db-462f5d353c76';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$this->mockTaskwarriorManager->expects($this->once())->method('exists')
->willReturn(true);
$this->mockTaskwarriorManager->expects($this->once())->method('updateTask');
$todo = new ToDo($this->mockTaskwarriorManager);
$todo->import($mockVTodo);
}
}
?>

170
tests/TaskTest.php Normal file
View File

@ -0,0 +1,170 @@
<?php
use Sabre\VObject\Component\VCalendar;
use Aerex\TaskwarriorPlugin\Taskwarrior\Task;
use Aerex\TaskwarriorPlugin\Plugin;
use Carbon\Carbon;
use Sabre\DAV\Server;
use DateTime;
class TaskTest extends \PHPUnit\Framework\TestCase {
/**
* @var Plugin
*
*/
private $plugin;
/**
* @var VCalendar
*/
private $cal;
/**
* @var Server
*/
private $server;
/**
* @var TaskwarriorCalendarEvent
*/
private $mockTaskCalEvent;
public function testSetDescriptionUsingDescription(){
$uuid = 'f987aa59-9031-4a7b-9cf3-6bfa4dc44a85';
$expectedDescription = 'This is a description';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$mockVTodo->DESCRIPTION = $expectedDescription;
$todo = new Task();
$todo->setDescription($mockVTodo);
$actualDescription = $todo->getDescription();
$this->assertEquals($expectedDescription, $actualDescription);
}
public function testSetDescriptionUsingSummary(){
$uuid = 'f987aa59-9031-4a7b-9cf3-6bfa4dc44a85';
$expectedDescription = 'This is a description';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$mockVTodo->SUMMARY = $expectedDescription;
$todo = new Task();
$todo->setDescription($mockVTodo);
$actualDescription = $todo->getDescription();
$this->assertEquals($expectedDescription, $actualDescription);
}
/**
* @expectedException Error
*/
public function testFailureSetDescription(){
$uuid = 'f987aa59-9031-4a7b-9cf3-6bfa4dc44a85';
$expectedDescription = 'This is a description';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$todo = new Task();
$todo->setDescription($mockVTodo);
$actualDescription = $todo->getDescription();
}
public function testSetEntryTime(){
$uuid = 'f987aa59-9031-4a7b-9cf3-6bfa4dc44a85';
$expectedEntryTime = new DateTime('2018-11-11');
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$expectedCarbonDate = new Carbon($expectedEntryTime->format('Y-m-d'));
$mockVTodo->DTSTAMP = $expectedEntryTime;
$todo = new Task();
$todo->setEntryTime($mockVTodo);
$actualEntryTime = $todo->getEntryTime();
$this->assertEquals($expectedCarbonDate, $actualEntryTime);
}
public function testSetStartTime(){
$uuid = 'f987aa59-9031-4a7b-9cf3-6bfa4dc44a85';
$expectedStartTime = new DateTime('2018-11-11');
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$expectedCarbonDate = new Carbon($expectedStartTime->format('Y-m-d'));
$mockVTodo->DTSTART = $expectedStartTime;
$todo = new Task();
$todo->setStartTime($mockVTodo);
$actualStartTime = $todo->getStartTime();
$this->assertEquals($expectedCarbonDate, $actualStartTime);
}
public function testSetModifiedTime(){
$uuid = '182a6301-98e6-44df-97eb-8c7620f25b43';
$expectedModifiedTime = new DateTime('2018-11-11');
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$expectedCarbonDate = new Carbon($expectedModifiedTime->format('Y-m-d'));
$mockVTodo->{'LAST-MODIFIED'} = $expectedModifiedTime;
$todo = new Task();
$todo->setModifiedTime($mockVTodo);
$actualModifiedTime = $todo->getModifiedTime();
$this->assertEquals($expectedCarbonDate, $actualModifiedTime);
}
public function testSetDue(){
$uuid = 'dde78b02-97d9-4e11-8603-e0fc14474c7c';
$expectedDueTime = new DateTime('2018-11-11');
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$expectedCarbonDate = new Carbon($expectedDueTime->format('Y-m-d'));
$mockVTodo->DUE = $expectedDueTime;
$todo = new Task();
$todo->setDue($mockVTodo);
$actualDue = $todo->getDue();
$this->assertEquals($expectedCarbonDate, $actualDue);
}
public function testSetCategories(){
$uuid = '182a6301-98e6-44df-97eb-8c7620f25b43';
$expectedCategories = array("home", "work");
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$mockVTodo->CATEGORIES = $expectedCategories;
$todo = new Task();
$todo->setCategories($mockVTodo);
$actualCategories = $todo->getCategories();
$this->assertEquals($expectedCategories, $actualCategories);
}
}

View File

@ -1,174 +0,0 @@
<?php
use Sabre\DAV\Exception\BadRequest;
use Monolog\Logger;
use Sabre\VObject\Component\VCalendar;
use Aerex\TaskwarriorPlugin\TaskwarriorManager;
use Aerex\TaskwarriorPlugin\iCalEventProcessor;
use Aerex\TaskwarriorPlugin\Config;
use DavidBadura\Taskwarrior\Task;
use DateTime;
class iCalEventProcessorTest extends \PHPUnit\Framework\TestCase {
/**
* @var \PHPUnit_Framework_MockObject_MockObject
* */
private $mockTaskwarrior;
/***
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $mockTaskwarriorConfig;
/***
* @var \PHPUnit_Framework__MockObject
*/
private $mockLogger;
function setup(){
$this->mockTaskwarrior = $this->createMock(TaskwarriorManager::class);
$this->mockTask = $this->createMock(Task::class);
$this->mockTaskwarriorConfig = $this->createMock(Config::class);
$this->mockLogger = $this->createMock(Logger::class);
}
function testConstructorWithConfig() {
$this->mockTaskwarriorConfig->expects($this->once())
->method('getTaskwarriorInstance')
->will($this->returnValue($this->mockTaskwarrior));
$this->taskCalEvent = new iCalEventProcessor($this->mockTaskwarriorConfig);
}
function testBuildToDoComponent(){
$uuid = '9f353281-1051-4c45-92db-462f5d353c76';
$startTime = new DateTime('2018-07-04');
$endTime = new DateTime('2018-07-06');
$modifiedTime = new DateTime('2018-08-05');
$categories = array('home', 'test');
$dueDate = new DateTime('2018-08-20');
$description = 'This is a simple todo';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$mockVTodo->add('DSTAMP', $startTime);
$mockVTodo->add('DUE', $dueDate);
$mockVTodo->add('LAST-MODIFIED',$modifiedTime);
$mockVTodo->add('DSTART', $startTime);
$mockVTodo->add('DTEND', $endTime);
$mockVTodo->add('DESCRIPTION', $description);
$mockVTodo->add('CATEGORIES', $categories);
// $this->mockTaskwarrior->expects($this->exactly(4))
// ->method('parseiCalDateTime')
// ->will($this->onConsecutiveCalls($startTime, $startTime, $modifiedTime, $endTime));
$this->mockTaskwarrior->expects($this->once())->method('createTask')->with($this->equalTo($uuid))
->willReturn($this->mockTask);
$this->mockTask->expects($this->once())->method('setDue')->with($this->equalTo($dueDate));
$this->mockTask->expects($this->once())->method('setDescription')->with($this->equalTo($description));
$this->mockTaskwarrior->expects($this->once())->method('convertToStringArray')->with($this->equalTo($mockVTodo->CATEGORIES))
->willReturn($categories);
$this->mockTask->expects($this->once())->method('setTags')
->with($this->equalTo($categories));
$this->mockTaskwarrior->expects($this->exactly(4))
->method('setValue');
$this->mockLogger->expects($this->never())->method('error');
$this->mockTaskwarriorConfig
->expects($this->once())
->method('getTaskwarriorInstance')
->will($this->returnValue($this->mockTaskwarrior));
$this
->mockTaskwarrior
->expects($this->once())
->method('save');
$twCalEvent = new iCalEventProcessor($this->mockTaskwarriorConfig);
$twCalEvent->importTask($mockVTodo);
}
/**
* @expectedException Sabre\DAV\Exception
*
*
*/
function testFailTaskNoComponentDefine(){
$this->mockTaskwarrior->expects($this->never())->method('createTask');
$this->mockTaskwarrior->expects($this->never())->method('setEntryTime');
$this->mockTaskwarrior->expects($this->never())->method('setEndTime');
$this->mockTaskwarrior->expects($this->never())->method('setDueDate');
$this->mockTaskwarrior->expects($this->never())->method('setSummary');
$this->mockTaskwarrior->expects($this->never())->method('setCategories');
$this->mockTaskwarrior->expects($this->never())->method('build');
$this->mockTaskwarrior->expects($this->never())->method('save');
$this->mockTaskwarrior->expects($this->never())->method('exists');
$this->mockTaskwarriorConfig->expects($this->once())->method('getTaskwarriorInstance')
->willReturn($this->mockTaskwarrior);
$this->mockLogger->expects($this->once())->method('error');
$this->mockTaskwarriorConfig->expects($this->once())->method('getLogger')
->willReturn($this->mockLogger);
$twCalEvent = new iCalEventProcessor($this->mockTaskwarriorConfig);
$twCalEvent->importTask();
}
/**
* @expectedException Sabre\DAV\Exception\BadRequest
*
*
*/
/*
function testFailTaskExists(){
$uuid = '9f353281-1051-4c45-92db-462f5d353c76';
$mockVCalendar = new VCalendar();
$mockVTodo = $mockVCalendar->add('VTODO', ['UID' => $uuid]);
$expectedErrorMessage = "already exists";
$this->mockTaskwarrior->expects($this->never())->method('createTask');
$this->mockTaskwarrior->expects($this->never())->method('setEntryTime');
$this->mockTaskwarrior->expects($this->never())->method('setEndTime');
$this->mockTaskwarrior->expects($this->never())->method('setDueDate');
$this->mockTaskwarrior->expects($this->never())->method('setSummary');
$this->mockTaskwarrior->expects($this->never())->method('setCategories');
$this->mockTaskwarrior->expects($this->never())->method('build');
$this->mockTaskwarrior->expects($this->never())->method('save');
$this->mockTaskwarrior->expects($this->once())->method('exists')
->willReturn(true);
$this->mockTaskwarriorConfig->expects($this->once())->method('getTaskwarriorInstance')
->willReturn($this->mockTaskwarrior);
$this->mockLogger->expects($this->once())->method('error');
$this->mockTaskwarriorConfig->expects($this->once())->method('getLogger')
->willReturn($this->mockLogger);
$twCalEvent = new iCalEventProcessor($this->mockTaskwarriorConfig);
$twCalEvent->importTask($mockVTodo);
}
*/
}
?>