Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
488cf14aac | |||
f713fbb16b | |||
68470e3dee | |||
a2b2073bad | |||
4d96cc4e8c |
@@ -15,7 +15,9 @@
|
|||||||
"php": ">=5.5",
|
"php": ">=5.5",
|
||||||
"sabre/dav": "~3.1.2",
|
"sabre/dav": "~3.1.2",
|
||||||
"sabre/vobject": "^4.0",
|
"sabre/vobject": "^4.0",
|
||||||
"easycorp/easy-log-handler": "^1.0"
|
"easycorp/easy-log-handler": "^1.0",
|
||||||
|
"zendframework/zend-validator": "^2.10",
|
||||||
|
"nesbot/carbon": "^1.14"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit" : "> 4.8, <=6.0.0"
|
"phpunit/phpunit" : "> 4.8, <=6.0.0"
|
||||||
|
1795
composer.lock
generated
1795
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Aerex\TaskwarriorPlugin\Processors;
|
namespace Aerex\TaskwarriorPlugin;
|
||||||
use Aerex\TaskwarriorPlugin\TaskwarriorManager;
|
use Aerex\TaskwarriorPlugin\Taskwarrior\TaskwarriorManager;
|
||||||
use Sabre\VObject\Component\VTodo;
|
use Sabre\VObject\Component\VTodo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ToDo
|
* Class CalendarProcessor
|
||||||
*
|
*
|
||||||
* @author Aerex
|
* @author Aerex
|
||||||
*/
|
*/
|
||||||
@@ -41,3 +41,4 @@ class CalendarProcessor
|
|||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Aerex\TaskwarriorPlugin\Configuration;
|
namespace Aerex\TaskwarriorPlugin;
|
||||||
|
|
||||||
use DavidBadura\Taskwarrior\Taskwarrior;
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Taskwarrior;
|
||||||
use Aerex\TaskwarriorPlugin\TaskwarriorManager;
|
use Aerex\TaskwarriorPlugin\Taskwarrior\TaskwarriorManager;
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
|
|
||||||
@@ -27,15 +27,35 @@ 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);
|
||||||
|
7
src/Exceptions/TaskwarriorCommandException.php
Normal file
7
src/Exceptions/TaskwarriorCommandException.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
class TaskwarriorCommandException extends Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
790
src/Plugin.php
790
src/Plugin.php
@@ -3,13 +3,15 @@
|
|||||||
namespace Aerex\TaskwarriorPlugin;
|
namespace Aerex\TaskwarriorPlugin;
|
||||||
|
|
||||||
use Sabre\DAV\Exception\BadRequest;
|
use Sabre\DAV\Exception\BadRequest;
|
||||||
use Sabre\VObject\Component\VCalendar;
|
use Sabre\VObject\Document;
|
||||||
use Sabre\HTTP\RequestInterface;
|
use Sabre\HTTP\RequestInterface;
|
||||||
use Sabre\HTTP\ResponseInterface;
|
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\CalendarProcessor;
|
use Aerex\TaskwarriorPlugin\CalendarProcessor;
|
||||||
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Taskwarrior;
|
||||||
|
use Aerex\TaskwarriorPlugin\Taskwarrior\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 CalendarProcessor();
|
$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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +80,18 @@ class Plugin extends ServerPlugin {
|
|||||||
*
|
*
|
||||||
* @param VCalendar $vCal parsed calendar object
|
* @param VCalendar $vCal parsed calendar object
|
||||||
*/
|
*/
|
||||||
function processCalendarEventForTaskwarrior(VCalendar $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,749 +107,19 @@ 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$response->setStatus(200);
|
||||||
|
$response->setBody('');
|
||||||
|
$response->setHeader('Content-Type', 'text/plain');
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 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.
|
* Returns a bunch of meta-data about the plugin.
|
||||||
|
@@ -3,8 +3,9 @@ namespace Aerex\TaskwarriorPlugin\Taskwarrior\Commands;
|
|||||||
|
|
||||||
use Aerex\TaskwarriorPlugin\Taskwarrior\Task;
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Task;
|
||||||
|
|
||||||
interface Strategy {
|
interface IStrategy {
|
||||||
public function add(Task $task);
|
public function add(Task $task);
|
||||||
|
public function count(Task $task);
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
@@ -1,62 +1,115 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace Aerex\TaskwarriorPlugin\Taskwarrior\Commands;
|
||||||
|
use Aerex\TaskwarriorPlugin\Taskwarrior\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 {
|
class TodoStrategy implements IStrategy {
|
||||||
|
private $config;
|
||||||
|
|
||||||
public function __construct(TaskwarriorConfig $config){
|
public function __construct(Config $config){
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->cmd[] = $this->config->taskBin();
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add(Task $task){
|
// Append on modifiers
|
||||||
$this->cmd[] = 'add';
|
foreach($taskJson as $prop => $value){
|
||||||
|
if(isset($value) && $value != null){
|
||||||
if($task->getDescription() != null){
|
$cmd[] = sprintf(' %s: %s ', $prop, $value);
|
||||||
$this->cmd[] = sprintf('"%s"', $task->getDescription());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($task->getCategories() != null){
|
|
||||||
$categories = implode(' +', $task->getCategories());
|
|
||||||
$this->cmd[] = $categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($task->getDue() != null){
|
|
||||||
$this->cmd[] = $task->getDue('Y-m-dTH:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
if($task->getRecurrence() != null){
|
|
||||||
$rrule = $task->getRecurrence()->getParts();
|
|
||||||
$this->cmd[] = sprintf('recur:%s', $rrule['FREQ']);
|
|
||||||
if(isset($rrule['UNTIL'])){
|
|
||||||
$this->cmd[] = sprintf('until:%s', $rrule['UNTIL']);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if($task->getStatus() != null){
|
|
||||||
$this->cmd[] = sprintf('status:%s', $task->getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->executeCommand($cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->executeCommand($cmd);
|
||||||
|
|
||||||
|
|
||||||
private function executeCommand($command){
|
|
||||||
|
|
||||||
|
|
||||||
$cmdString = implode(' ', $command);
|
|
||||||
$process = new Process($cmdString);
|
|
||||||
|
|
||||||
$process->run();
|
|
||||||
|
|
||||||
if(!$process->isSuccessful()){
|
|
||||||
throw new TaskwarriorCommandLineException($process);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $process->getOutput();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Aerex\TaskwarriorPlugin\Taskwarrior;
|
namespace Aerex\TaskwarriorPlugin\Taskwarrior;
|
||||||
@@ -28,108 +29,94 @@ class Task {
|
|||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @JMS\Type("string")
|
|
||||||
*/
|
*/
|
||||||
private $priority;
|
private $priority;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @JMS\Type("string")
|
|
||||||
*/
|
*/
|
||||||
private $project;
|
private $project;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $due;
|
private $due;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $wait;
|
private $wait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*
|
*
|
||||||
* @JMS\Type("array<string>")
|
|
||||||
*/
|
*/
|
||||||
private $tags;
|
private $tags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float
|
* @var float
|
||||||
*
|
*
|
||||||
* @JMS\Type("float")
|
|
||||||
*/
|
*/
|
||||||
private $urgency;
|
private $urgency;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $entry;
|
private $entry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $start;
|
private $start;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @JMS\Type("Recurring")
|
|
||||||
*/
|
*/
|
||||||
private $recur;
|
private $recur;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $until;
|
private $until;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Annotation[]
|
* @var Annotation[]
|
||||||
*
|
*
|
||||||
* @JMS\Type("array<Aerex\Taskwarrior\Annotation>")
|
|
||||||
*/
|
*/
|
||||||
private $annotations = [];
|
private $annotations = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $modified;
|
private $modified;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Carbon
|
* @var Carbon
|
||||||
*
|
*
|
||||||
* @JMS\Type("Carbon")
|
|
||||||
*/
|
*/
|
||||||
private $end;
|
private $end;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @JMS\Type("string")
|
|
||||||
*/
|
*/
|
||||||
private $status;
|
private $status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Task[]|ArrayCollection
|
* @var Task[]|ArrayCollection
|
||||||
*
|
*
|
||||||
* @JMS\Type("Depends")
|
|
||||||
*/
|
*/
|
||||||
private $depends;
|
private $depends;
|
||||||
|
|
||||||
|
|
||||||
public function __construct($UUID=null){
|
public function __construct($UUID=null){
|
||||||
$validator = new Uuid();
|
$validator = new Uuid();
|
||||||
|
|
||||||
@@ -144,10 +131,53 @@ class Task {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
*
|
||||||
* If description is not available attempt to summary, otherwise throw Exception
|
* If description is not available attempt to summary, otherwise throw Exception
|
||||||
*/
|
*/
|
||||||
@@ -207,7 +237,11 @@ public function setDue(VTodo $document){
|
|||||||
$this->due = new Carbon($document->DUE->getDateTime()->format(\DateTime::W3C));
|
$this->due = new Carbon($document->DUE->getDateTime()->format(\DateTime::W3C));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function getDue(){
|
public function getDue($format=null){
|
||||||
|
if($format != null){
|
||||||
|
return $this->due->format($format);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->due;
|
return $this->due;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +281,10 @@ public function setStatus(VTodo $document){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRecurrence(){
|
||||||
|
return $this->recur;
|
||||||
|
|
||||||
|
}
|
||||||
public function setRecurrence(VTodo $document){
|
public function setRecurrence(VTodo $document){
|
||||||
if(isset($document->RRULE)){
|
if(isset($document->RRULE)){
|
||||||
$this->recur = $document->RRULE;
|
$this->recur = $document->RRULE;
|
||||||
@@ -262,3 +300,4 @@ public function getStatus(){
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,12 +3,15 @@
|
|||||||
namespace Aerex\TaskwarriorPlugin\Taskwarrior;
|
namespace Aerex\TaskwarriorPlugin\Taskwarrior;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Aerex\TaskwarriorPlugin\Commands\IStrategy;
|
use Aerex\TaskwarriorPlugin\Commands\IStrategy;
|
||||||
|
use Aerex\TaskwarriorPlugin\Config;
|
||||||
|
|
||||||
class Taskwarrior {
|
class Taskwarrior {
|
||||||
|
|
||||||
const EXPORT = 'export';
|
|
||||||
const IMPORT = 'import';
|
/**
|
||||||
const ADD = 'add';
|
* @var IStrategy
|
||||||
|
*/
|
||||||
|
private $strategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TaskwarriorConfig
|
* @var TaskwarriorConfig
|
||||||
@@ -39,14 +42,17 @@ class Taskwarrior {
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function __construct(TaskwarriorConfig $config){
|
public function __construct(Config $config){
|
||||||
if(!isset($config)){
|
if(!isset($config)){
|
||||||
$this->config = new TaskwarriorConfig();
|
$this->config = new Config();
|
||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
public function getConfig(){
|
||||||
|
return $this->config;
|
||||||
|
}
|
||||||
|
|
||||||
public function setStrategy(IStrategy $strategy){
|
public function setStrategy($strategy){
|
||||||
$this->strategy = $strategy;
|
$this->strategy = $strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +63,22 @@ class Taskwarrior {
|
|||||||
|
|
||||||
|
|
||||||
public function add($task){
|
public function add($task){
|
||||||
$this->strategy($task);
|
$this->strategy->add($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count($uuid){
|
||||||
|
return $this->strategy->count($uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function modify($task){
|
||||||
|
return $this->strategy->modify($task);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,71 +1,61 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Aerex\TaskwarriorPlugin;
|
namespace Aerex\TaskwarriorPlugin\Taskwarrior;
|
||||||
|
|
||||||
use Aerex\TaskwarriorPlugin\Taskwarrior;
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Taskwarrior;
|
||||||
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Task;
|
||||||
|
use Aerex\TaskwarriorPlugin\Taskwarrior\Commands\TodoStrategy;
|
||||||
use Sabre\VObject\Component\VTodo;
|
use Sabre\VObject\Component\VTodo;
|
||||||
use DavidBadura\Taskwarrior\TaskManager;
|
|
||||||
use DavidBadura\Taskwarrior\Task;
|
|
||||||
|
|
||||||
class TaskwarriorManager {
|
class TaskwarriorManager {
|
||||||
|
|
||||||
const DESCRIPTION = 'description';
|
|
||||||
const CATEGORIES = 'categories';
|
|
||||||
const TASK_UUID = 'uuid';
|
|
||||||
const ICAL_UID = 'uid';
|
|
||||||
|
|
||||||
private $tasks;
|
|
||||||
|
|
||||||
const ENTRY = 'entry';
|
/**
|
||||||
const START = 'start';
|
* @var TodoStrategy
|
||||||
const MODIFIED = 'modified';
|
*/
|
||||||
const END = 'end';
|
private $todoStrategy;
|
||||||
|
|
||||||
|
|
||||||
public function __construct($taskwarrior){
|
public function __construct($taskwarrior){
|
||||||
parent::__construct($taskwarrior);
|
if(!isset($taskwarrior)){
|
||||||
|
$this->taskwarrior = new Taskwarrior();
|
||||||
|
} else {
|
||||||
|
$this->taskwarrior = $taskwarrior;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize strategies
|
||||||
|
$this->todoStrategy = new TodoStrategy($this->taskwarrior->getConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTask($UUID){
|
|
||||||
$task = new Task();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function export(VTodo $document){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addTask(VTodo $document){
|
public function addTask(VTodo $document){
|
||||||
|
|
||||||
$task = $this->taskwarrior->createTask($document->UID);
|
$this->taskwarrior->setStrategy($this->todoStrategy);
|
||||||
|
|
||||||
$task->setDescription($document);
|
$task = $this->taskwarrior->createTask((string)$document->UID);
|
||||||
|
|
||||||
$task->setEntry($document);
|
$task->setDescription($document);
|
||||||
|
|
||||||
$task->setStartTime($document);
|
$task->setEntryTime($document);
|
||||||
|
|
||||||
$task->setModifiedTime($document);
|
$task->setDue($document);
|
||||||
|
|
||||||
$task->setStopTime($document);
|
$task->setCategories($document);
|
||||||
|
|
||||||
$task->setDue($document);
|
$task->setRecurrence($document);
|
||||||
|
|
||||||
$task->setCategories($document);
|
return $this->taskwarrior->add($task);
|
||||||
|
|
||||||
$task->setStatus($document);
|
|
||||||
|
|
||||||
$task->setRecurrence($document);
|
|
||||||
|
|
||||||
return $this->taskwarrior->add($task);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTask(VTodo $document){
|
public function updateTask(VTodo $document){
|
||||||
|
|
||||||
$task = $this->taskwarrior->get($document->UID);
|
$task = $this->taskwarrior->createTask((string)$document->UID);
|
||||||
|
|
||||||
$task->setDescription($document);
|
$task->setDescription($document);
|
||||||
|
|
||||||
$task->setEntry($document);
|
$task->setEntryTime($document);
|
||||||
|
|
||||||
$task->setStartTime($document);
|
$task->setStartTime($document);
|
||||||
|
|
||||||
@@ -86,73 +76,20 @@ class TaskwarriorManager {
|
|||||||
return $upatedTask;
|
return $upatedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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){
|
public function taskExists($taskUuid){
|
||||||
$taskIsInCache = isset($this->cachedTasks[$taskUuid]);
|
|
||||||
|
|
||||||
if($taskIsInCache){
|
$this->taskwarrior->setStrategy($this->todoStrategy);
|
||||||
|
$exists = $this->taskwarrior->count((string)$taskUuid);
|
||||||
|
echo("exists " . $exists);
|
||||||
|
if($exists === "1"){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
$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){}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user