2015-02-06 05:38:54 -06:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace DavidBadura\Taskwarrior;
|
|
|
|
|
2015-04-06 16:13:02 -05:00
|
|
|
use Carbon\Carbon;
|
|
|
|
use DavidBadura\Taskwarrior\Exception\TaskwarriorException;
|
2015-02-08 05:23:14 -06:00
|
|
|
use DavidBadura\Taskwarrior\Serializer\Handler\CarbonHandler;
|
2015-02-08 07:00:15 -06:00
|
|
|
use DavidBadura\Taskwarrior\Serializer\Handler\RecurringHandler;
|
2015-02-08 05:23:14 -06:00
|
|
|
use JMS\Serializer\Handler\HandlerRegistryInterface;
|
2015-04-03 15:27:45 -05:00
|
|
|
use JMS\Serializer\JsonSerializationVisitor;
|
|
|
|
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
|
|
|
|
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
|
2015-02-08 05:23:14 -06:00
|
|
|
use JMS\Serializer\Serializer;
|
2015-02-06 05:38:54 -06:00
|
|
|
use JMS\Serializer\SerializerBuilder;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author David Badura <d.a.badura@gmail.com>
|
|
|
|
*/
|
|
|
|
class TaskManager
|
|
|
|
{
|
|
|
|
/**
|
2015-02-06 06:14:58 -06:00
|
|
|
* @var Taskwarrior
|
2015-02-06 05:38:54 -06:00
|
|
|
*/
|
|
|
|
private $taskwarrior;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Task[]
|
|
|
|
*/
|
|
|
|
private $tasks = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Taskwarrior $taskwarrior
|
|
|
|
*/
|
|
|
|
public function __construct(Taskwarrior $taskwarrior)
|
|
|
|
{
|
|
|
|
$this->taskwarrior = $taskwarrior;
|
|
|
|
}
|
|
|
|
|
2015-02-06 06:14:58 -06:00
|
|
|
/**
|
|
|
|
* @return Taskwarrior
|
|
|
|
*/
|
|
|
|
public function getTaskwarrior()
|
|
|
|
{
|
|
|
|
return $this->taskwarrior;
|
|
|
|
}
|
|
|
|
|
2015-02-06 05:38:54 -06:00
|
|
|
/**
|
|
|
|
* @param Task $task
|
2015-04-03 15:27:45 -05:00
|
|
|
* @throws TaskwarriorException
|
2015-02-06 05:38:54 -06:00
|
|
|
*/
|
|
|
|
public function save(Task $task)
|
|
|
|
{
|
2015-04-03 15:27:45 -05:00
|
|
|
$errors = $this->validate($task);
|
|
|
|
|
|
|
|
if ($errors) {
|
|
|
|
throw new TaskwarriorException(implode(', ', $errors));
|
|
|
|
}
|
2015-04-03 09:39:19 -05:00
|
|
|
|
2015-02-06 05:38:54 -06:00
|
|
|
if (!$task->getUuid()) {
|
|
|
|
$this->add($task);
|
|
|
|
} else {
|
|
|
|
$this->edit($task);
|
|
|
|
}
|
2015-02-06 17:22:17 -06:00
|
|
|
|
|
|
|
$this->refresh($task);
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $uuid
|
|
|
|
* @return Task
|
|
|
|
* @throws TaskwarriorException
|
|
|
|
*/
|
|
|
|
public function find($uuid)
|
|
|
|
{
|
|
|
|
if (isset($this->tasks[$uuid])) {
|
|
|
|
return $this->tasks[$uuid];
|
|
|
|
}
|
|
|
|
|
|
|
|
$tasks = $this->filterAll($uuid);
|
|
|
|
|
|
|
|
if (count($tasks) == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($tasks) == 1) {
|
|
|
|
return $tasks[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new TaskwarriorException();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string|array $filter
|
|
|
|
* @return Task[]
|
|
|
|
*/
|
|
|
|
public function filterAll($filter = null)
|
|
|
|
{
|
|
|
|
if (is_string($filter)) {
|
|
|
|
$filter = explode(' ', $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = $this->export($filter);
|
|
|
|
|
|
|
|
foreach ($result as $key => $task) {
|
|
|
|
if (isset($this->tasks[$task->getUuid()])) {
|
|
|
|
|
|
|
|
$result[$key] = $this->tasks[$task->getUuid()];
|
2015-02-06 17:22:17 -06:00
|
|
|
$this->merge($result[$key], $task);
|
2015-02-06 05:38:54 -06:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->tasks[$task->getUuid()] = $task;
|
|
|
|
}
|
|
|
|
|
2015-04-06 08:36:55 -05:00
|
|
|
return $this->sort($result);
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string|array $filter
|
|
|
|
* @return Task[]
|
|
|
|
*/
|
|
|
|
public function filter($filter = null)
|
|
|
|
{
|
2015-04-06 08:36:55 -05:00
|
|
|
return $this->filterAll($filter . ' status:pending');
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Task $task
|
|
|
|
*/
|
|
|
|
public function delete(Task $task)
|
|
|
|
{
|
|
|
|
if (!$task->getUuid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-06 16:13:02 -05:00
|
|
|
if ($task->isRecurring()) {
|
|
|
|
$task->setUntil('now');
|
|
|
|
$this->save($task);
|
|
|
|
} else {
|
|
|
|
$this->taskwarrior->delete($task->getUuid());
|
|
|
|
$this->refresh($task);
|
|
|
|
}
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Task $task
|
|
|
|
*/
|
|
|
|
public function done(Task $task)
|
|
|
|
{
|
|
|
|
if (!$task->getUuid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->taskwarrior->done($task->getUuid());
|
2015-02-06 17:22:17 -06:00
|
|
|
$this->refresh($task);
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
2015-02-10 14:49:30 -06:00
|
|
|
/**
|
|
|
|
* @param Task $task
|
|
|
|
*/
|
|
|
|
public function reopen(Task $task)
|
|
|
|
{
|
|
|
|
if (!$task->getUuid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-03 15:27:45 -05:00
|
|
|
if ($task->isPending() || $task->isWaiting() || $task->isRecurring()) {
|
2015-02-10 14:49:30 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->taskwarrior->modify([
|
|
|
|
'status' => Task::STATUS_PENDING
|
|
|
|
], $task->getUuid());
|
|
|
|
|
|
|
|
$this->refresh($task);
|
|
|
|
}
|
|
|
|
|
2015-04-03 15:27:45 -05:00
|
|
|
/**
|
|
|
|
* @param Task $task
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function validate(Task $task)
|
|
|
|
{
|
|
|
|
$errors = [];
|
|
|
|
|
|
|
|
if ($task->isRecurring() && !$task->getDue()) {
|
|
|
|
$errors[] = 'You cannot remove the due date from a recurring task.';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($task->isRecurring() && !$task->getRecurring()) {
|
|
|
|
$errors[] = 'You cannot remove the recurrence from a recurring task.';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($task->getRecurring() && !$task->getDue()) {
|
|
|
|
$errors[] = "A recurring task must also have a 'due' date.";
|
|
|
|
}
|
|
|
|
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
|
2015-02-06 06:08:13 -06:00
|
|
|
/**
|
2015-02-06 17:22:17 -06:00
|
|
|
* @param Task $task
|
2015-02-06 06:08:13 -06:00
|
|
|
*/
|
2015-02-06 17:22:17 -06:00
|
|
|
public function refresh(Task $task)
|
2015-02-06 06:08:13 -06:00
|
|
|
{
|
2015-02-06 17:22:17 -06:00
|
|
|
$clean = $this->export($task->getUuid())[0];
|
|
|
|
$this->merge($task, $clean);
|
2015-02-06 06:08:13 -06:00
|
|
|
}
|
|
|
|
|
2015-02-06 05:38:54 -06:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function clear()
|
|
|
|
{
|
|
|
|
$this->tasks = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string|array $filter
|
|
|
|
* @return Task[]
|
|
|
|
*/
|
|
|
|
private function export($filter = null)
|
|
|
|
{
|
2015-02-08 06:13:53 -06:00
|
|
|
$this->update();
|
2015-02-06 05:38:54 -06:00
|
|
|
$json = $this->taskwarrior->export($filter);
|
|
|
|
|
2015-02-08 05:23:14 -06:00
|
|
|
return $this->getSerializer()->deserialize($json, 'array<DavidBadura\Taskwarrior\Task>', 'json');
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Task $task
|
|
|
|
* @throws TaskwarriorException
|
|
|
|
*/
|
|
|
|
private function add(Task $task)
|
|
|
|
{
|
|
|
|
$json = $this->serializeTask($task);
|
|
|
|
$uuid = $this->taskwarrior->import($json);
|
|
|
|
|
|
|
|
$this->setValue($task, 'uuid', $uuid);
|
|
|
|
$this->tasks[$uuid] = $task;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Task $task
|
2015-04-03 09:36:58 -05:00
|
|
|
* @throws TaskwarriorException
|
2015-02-06 05:38:54 -06:00
|
|
|
*/
|
|
|
|
private function edit(Task $task)
|
|
|
|
{
|
2015-02-06 06:08:13 -06:00
|
|
|
$this->taskwarrior->modify(
|
|
|
|
[
|
|
|
|
'description' => $task->getDescription(),
|
|
|
|
'project' => $task->getProject(),
|
2015-02-06 11:05:29 -06:00
|
|
|
'priority' => $task->getPriority(),
|
2015-02-06 11:31:13 -06:00
|
|
|
'tags' => $task->getTags(),
|
2015-02-06 06:08:13 -06:00
|
|
|
'due' => $task->getDue() ? $task->getDue()->format('Ymd\THis\Z') : null,
|
2015-02-06 17:22:17 -06:00
|
|
|
'wait' => $task->getWait() ? $task->getWait()->format('Ymd\THis\Z') : null,
|
2015-02-08 12:32:51 -06:00
|
|
|
'until' => $task->getUntil() ? $task->getUntil()->format('Ymd\THis\Z') : null,
|
|
|
|
'recur' => $task->getRecurring() ? $task->getRecurring()->getValue() : null,
|
2015-02-06 06:08:13 -06:00
|
|
|
],
|
|
|
|
$task->getUuid()
|
|
|
|
);
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-02-06 17:22:17 -06:00
|
|
|
* @param Task $old
|
|
|
|
* @param Task $new
|
2015-02-06 05:38:54 -06:00
|
|
|
*/
|
2015-02-06 17:22:17 -06:00
|
|
|
private function merge(Task $old, Task $new)
|
2015-02-06 05:38:54 -06:00
|
|
|
{
|
2015-02-06 17:22:17 -06:00
|
|
|
$this->setValue($old, 'urgency', $new->getUrgency());
|
|
|
|
$this->setValue($old, 'status', $new->getStatus());
|
|
|
|
$this->setValue($old, 'modified', $new->getModified());
|
2015-02-10 14:49:30 -06:00
|
|
|
|
|
|
|
if ($new->isPending()) { // fix reopen problem
|
|
|
|
$this->setValue($old, 'end', null);
|
|
|
|
} else {
|
|
|
|
$this->setValue($old, 'end', $new->getEnd());
|
|
|
|
}
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
2015-02-08 06:13:53 -06:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private function update()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->taskwarrior->command('list');
|
|
|
|
} catch (TaskwarriorException $e) {
|
2015-02-08 09:40:37 -06:00
|
|
|
// do nothing
|
2015-02-08 06:13:53 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 05:38:54 -06:00
|
|
|
/**
|
|
|
|
* @param Task[] $tasks
|
|
|
|
* @return Task[]
|
|
|
|
*/
|
|
|
|
private function sort(array $tasks)
|
|
|
|
{
|
|
|
|
usort(
|
|
|
|
$tasks,
|
|
|
|
function (Task $a, Task $b) {
|
|
|
|
if (0 != $diff = $b->getUrgency() - $a->getUrgency()) {
|
|
|
|
return $diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $a->getEntry() >= $b->getEntry() ? 1 : -1;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return $tasks;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param Task $task
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function serializeTask(Task $task)
|
|
|
|
{
|
2015-02-08 05:23:14 -06:00
|
|
|
$result = $this->getSerializer()->serialize($task, 'json');
|
2015-02-06 05:38:54 -06:00
|
|
|
|
|
|
|
return str_replace("\\/", "/", $result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-02-06 17:22:17 -06:00
|
|
|
* @param Task $task
|
2015-02-06 05:38:54 -06:00
|
|
|
* @param string $attr
|
2015-02-06 17:22:17 -06:00
|
|
|
* @param mixed $value
|
2015-02-06 05:38:54 -06:00
|
|
|
*/
|
|
|
|
private function setValue(Task $task, $attr, $value)
|
|
|
|
{
|
2015-02-08 07:29:44 -06:00
|
|
|
$refClass = new \ReflectionClass('DavidBadura\Taskwarrior\Task');
|
|
|
|
$refProp = $refClass->getProperty($attr);
|
|
|
|
$refProp->setAccessible(true);
|
|
|
|
$refProp->setValue($task, $value);
|
2015-02-06 05:38:54 -06:00
|
|
|
}
|
|
|
|
|
2015-02-08 05:23:14 -06:00
|
|
|
/**
|
|
|
|
* @return Serializer
|
|
|
|
*/
|
|
|
|
private function getSerializer()
|
|
|
|
{
|
2015-04-03 15:27:45 -05:00
|
|
|
$propertyNamingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
|
|
|
|
|
|
|
|
$visitor = new JsonSerializationVisitor($propertyNamingStrategy);
|
|
|
|
$visitor->setOptions(JSON_UNESCAPED_UNICODE);
|
|
|
|
|
2015-02-08 05:23:14 -06:00
|
|
|
return SerializerBuilder::create()
|
2015-04-03 15:27:45 -05:00
|
|
|
->setPropertyNamingStrategy($propertyNamingStrategy)
|
2015-02-08 05:23:14 -06:00
|
|
|
->configureHandlers(function (HandlerRegistryInterface $registry) {
|
|
|
|
$registry->registerSubscribingHandler(new CarbonHandler());
|
2015-02-08 07:00:15 -06:00
|
|
|
$registry->registerSubscribingHandler(new RecurringHandler());
|
2015-02-08 05:23:14 -06:00
|
|
|
})
|
|
|
|
->addDefaultHandlers()
|
2015-04-03 15:27:45 -05:00
|
|
|
->setSerializationVisitor('json', $visitor)
|
|
|
|
->addDefaultDeserializationVisitors()
|
2015-02-08 05:23:14 -06:00
|
|
|
->build();
|
|
|
|
}
|
|
|
|
|
2015-02-06 05:38:54 -06:00
|
|
|
/**
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
public static function create()
|
|
|
|
{
|
|
|
|
return new self(new Taskwarrior());
|
|
|
|
}
|
|
|
|
}
|