diff --git a/config.yaml b/config.yaml index 407cae9..48b276c 100755 --- a/config.yaml +++ b/config.yaml @@ -4,4 +4,4 @@ logger: taskwarrior: taskdata: /home/aerex/.task taskrc: /home/aerex/.taskrc - project_tag_suffix: project_ + project_category_prefix: project_ diff --git a/lib/Browser.php b/lib/Browser.php new file mode 100644 index 0000000..40cfd35 --- /dev/null +++ b/lib/Browser.php @@ -0,0 +1,10 @@ +end() ->end() ->end() - ->scalarNode('timezone') - ->defaultValue('UTC') - ->validate() - ->IfNotInArray(CarbonTimeZone::listIdentifiers()) - ->thenInvalid('Invalid timezone identifier %s') - ->end() - ->end() - ->end() + ->end() ->end() ->arrayNode('storages') ->children(); @@ -67,4 +59,9 @@ class ConfigBuilder implements ConfigurationInterface { $parseContents = Yaml::parse($contents); return $this->processor->processConfiguration($this, [$parseContents]); } + + public function saveConfigs($configs) { + $yaml = Yaml::dump($configs, 3, 2); + file_put_contents($this->configFile, $yaml); + } } diff --git a/lib/Configs/TaskwarriorConfig.php b/lib/Configs/TaskwarriorConfig.php index f80fb03..e02fcc0 100644 --- a/lib/Configs/TaskwarriorConfig.php +++ b/lib/Configs/TaskwarriorConfig.php @@ -11,12 +11,15 @@ class TaskwarriorConfig { ->children() ->scalarNode('taskdata') ->defaultValue('~/.task') + ->info('The environment variable overrides the default and the command line, and the "data.location" configuration setting of the task data directory') ->end() ->scalarNode('taskrc') ->defaultValue('~/.taskrc') + ->info('The enivronment variable overrides the default and the command line specification of the .taskrc file') ->end() - ->scalarNode('project_tag_suffix') + ->scalarNode('project_category_prefix') ->defaultValue('project_') + ->info('The word after the given prefix for a iCal category will be used to identify a task\'s project') ->end() ->end(); diff --git a/lib/Logger.php b/lib/Logger.php index b01e9b9..3043d3b 100644 --- a/lib/Logger.php +++ b/lib/Logger.php @@ -69,4 +69,10 @@ class Logger { } } + public function setLevel($level) { + if ($this->configs['enabled']) { + $this->logger->setLevel($level); + } + } + } diff --git a/lib/Plugin.php b/lib/Plugin.php index 5e2fcea..b5cca28 100644 --- a/lib/Plugin.php +++ b/lib/Plugin.php @@ -3,6 +3,7 @@ namespace Aerex\BaikalStorage; use Aerex\BaikalStorage\Logger; +use Monolog\Logger as Monolog; use Aerex\BaikalStorage\Storages\Taskwarrior; use Aerex\BaikalStorage\Configs\ConfigBuilder; use Aerex\BaikalStorage\Configs\TaskwarriorConfig; @@ -32,6 +33,9 @@ class Plugin extends ServerPlugin { */ protected $storageManager; + protected $rawConfigs; + + /** * Creates the Storage plugin @@ -40,9 +44,9 @@ class Plugin extends ServerPlugin { * */ function __construct($configFile){ - $configs = $this->buildConfigurations($configFile); - $this->storageManager = new StorageManager($configs); - $this->initializeStorages($configs); + $this->rawConfigs = $this->buildConfigurations($configFile); + $this->storageManager = new StorageManager($this->rawConfigs); + $this->initializeStorages($this->rawConfigs); } public function buildConfigurations($configFile) { @@ -57,7 +61,7 @@ class Plugin extends ServerPlugin { */ public function initializeStorages($configs) { - $taskwarrior = new Taskwarrior(new Console(['rc.verbose=nothing', 'rc.hooks=off']), $configs, new Logger($configs, 'Taskwarrior');); + $taskwarrior = new Taskwarrior(new Console(['rc.verbose=nothing', 'rc.hooks=off']), $configs, new Logger($configs, 'Taskwarrior')); $this->storageManager->addStorage(Taskwarrior::NAME, $taskwarrior); } @@ -82,14 +86,13 @@ class Plugin extends ServerPlugin { */ function getPluginName() { - return 'taskwarrior'; + return 'baikal-storage'; } /** * This method is called before any HTTP method handler. * - * This method intercepts any GET, DELETE, PUT and PROPFIND calls to - * filenames that are known to match the 'temporary file' regex. + * This method intercepts any GET, DELETE, PUT and PROPFIND. * * @param RequestInterface $request * @param ResponseInterface $response @@ -102,8 +105,11 @@ class Plugin extends ServerPlugin { switch ($request->getMethod()) { case 'PUT': $this->httpPut($request, $response); + break; + case 'POST': + $this->httpPost($request, $response); + return; } - return; } @@ -130,6 +136,111 @@ class Plugin extends ServerPlugin { } + /** + * This method handles the POST method. + * + * @param RequestInterface $request + * + * @return bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + $postVars = $request->getPostData(); + $body = $request->getBodyAsString(); + if (isset($postVars['baikalStorage'])) { + foreach ($this->storageManager->getStorages() as $storage) { + if ($storage::NAME == $postVars['baikalStorage'] + && $postVars['baikalStorageAction'] == 'saveConfigs') { + $updateStorageConfigs = $storage->updateConfigs($postVars); + $this->rawConfigs['storages'][$postVars['baikalStorage']] = $updateStorageConfigs; + } + } + + } + if (isset($postVars['logLevel'])) { + $this->rawConfigs['general']['logger']['level'] = $postVars['logLevel']; + } + if (isset($postVars['logFilePath'])) { + $this->rawConfigs['general']['logger']['file'] = $postVars['logFilePath']; + } + + $this->config->saveConfigs($this->rawConfigs); + + $response->setHeader('Location', $request->getUrl()); + $response->setStatus(302); + $request->setBody($body); + + + } + + /** + * Generates the 'general' configuration section + * @return string + */ + + public function generateGeneralConfigSection() { + $configuredLogLevel = ''; + $logFilePath = ''; + if (isset($this->rawConfigs['general']) + && isset($this->rawConfigs['general']['logger']) + && $this->rawConfigs['general']['logger']['enabled']) { + $configuredLogLevel = $this->rawConfigs['general']['logger']['level']; + $logFilePath = $this->rawConfigs['general']['logger']['file']; + } + $html = '
'; + $html .= '

Configuration - Baikal Storage

'; + $html .= '

general

'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
log levelThe minimum log level '; + $html .= ''; + + $html .= '
log file pathThe absolute file path of the log
'; + $html .= '
'; + return $html; + } + + /** + * Returns a html to display an optional configuration page for the plugin + * @return array + */ + public function getConfigBrowser() { + $html = $this->generateGeneralConfigSection(); + + foreach ($this->storageManager->getStorages() as $storage) { + $html .= '
'; + $html .= '

' . $storage::NAME . '

'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= $storage->getConfigBrowser(); + $html .= '
'; + $html .= '
'; + $html .= ''; + $html .= ''; + } + + return $html; + } + /** * Returns a bunch of meta-data about the plugin. * @@ -147,6 +258,7 @@ class Plugin extends ServerPlugin { 'name' => $this->getPluginName(), 'description' => 'The plugin provides synchronization between taskwarrior tasks and iCAL events', 'link' => null, + 'config' => true ]; } diff --git a/lib/Storages/IStorage.php b/lib/Storages/IStorage.php index f35407f..7b4233f 100644 --- a/lib/Storages/IStorage.php +++ b/lib/Storages/IStorage.php @@ -7,5 +7,6 @@ use Sabre\VObject\Component\VCalendar as Calendar; interface IStorage { public function save(Calendar $c); public function refresh(); - public function getConfig(); + public function getConfigBrowser(); + public function updateConfigs($postData); } diff --git a/lib/Storages/Taskwarrior.php b/lib/Storages/Taskwarrior.php index 996c108..cb57b78 100644 --- a/lib/Storages/Taskwarrior.php +++ b/lib/Storages/Taskwarrior.php @@ -3,8 +3,6 @@ namespace Aerex\BaikalStorage\Storages; use Sabre\VObject\Component\VCalendar as Calendar; -use Carbon\Carbon; -use Carbon\CarbonTimeZone; class Taskwarrior implements IStorage { @@ -12,17 +10,52 @@ class Taskwarrior implements IStorage { private $tasks = []; private $configs; private $logger; - private $tz; public function __construct($console, $configs, $logger) { $this->console = $console; $this->configs = $configs['storages']['taskwarrior']; $this->logger = $logger; - $this->tz = new CarbonTimeZone($configs['general']['timezone']); } - public function getConfig() { - return $this->config; + public function getConfigBrowser() { + $html = ''; + $html .= 'taskrc'; + $html .= 'The enivronment variable overrides the default and the command line specification of the .taskrc file'; + $html .= ''; + $html .= ''; + $html = ''; + $html .= 'taskrc'; + $html .= 'The enivronment variable overrides the default and the command line specification of the .taskrc file'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= 'taskdata'; + $html .= 'The environment variable overrides the default and the command line, and the "data.location" configuration setting of the task data directory'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= 'project_category_prefix'; + $html .= "The word after the given prefix for a iCal category will be used to identify a task's project"; + $html .= ''; + $html .= ''; + return $html; + } + + public function updateConfigs($postData) { + if (isset($postData['tw_taskrc'])) { + $this->configs['taskrc'] = $postData['tw_taskrc']; + } + + if (isset($postData['tw_taskdata'])){ + $this->configs['taskdata'] = $postData['tw_taskdata']; + } + + if (isset($postData['tw_project_category_prefix'])){ + $this->configs['project_category_prefix'] = $postData['tw_project_category_prefix']; + } + + return $this->configs; + } public function refresh() { @@ -48,24 +81,41 @@ class Taskwarrior implements IStorage { if (isset($vtodo->SUMMARY)){ $task['description'] = (string)$vtodo->SUMMARY; - } else if(isset($vtodo->DESCRIPTION)) { - $task['description'] = (string)$vtodo->DESCRIPTION; - } - - if (isset($vtodo->DTSTAMP)){ - $task['entry'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C), $this->tz); } + if (isset($vtodo->DESCRIPTION)) { + $annotations = []; + if (isset($task['annotations'])) { + $annotations = $task['annotations']; + } + $task['annotations'] = []; + $descriptionLines = explode('\n', $vtodo->DESCRIPTION); + foreach ($descriptionLines as $key => $descriptionLine) { + $annotationEntry = $vtodo->DTSTAMP->getDateTime()->modify("+$key second")->format(\DateTime::ISO8601); + foreach ($annotations as $annotation) { + if ($annotation['description'] == $descriptionLine) { + $annotationEntry = $annotation['entry']; + break; + } + } + array_push(['description' => $descriptionLine, 'entry' => $annotationEntry]); + } + } + + if (!isset($task['entry'])){ + $task['entry'] = $vtodo->DTSTAMP->getDateTime()->format(\DateTime::ISO8601); + } + if (isset($vtodo->DTSTART)) { - $task['start'] = new Carbon($vtodo->DTSTART->getDateTime()->format(\DateTime::W3C), $this->tz); + $task['start'] = $vtodo->DTSTART->getDateTime()->format(\DateTime::ISO8601); } if (isset($vtodo->DTEND)){ - $task['end'] = new Carbon($vtodo->DTEND->getDateTime()->format(\DateTime::W3C), $this->tz); + $task['end'] = $vtodo->DTEND->getDateTime()->format(\DateTime::ISO8601); } if (isset($vtodo->{'LAST-MODIFIED'})) { - $task['modified'] = new Carbon($vtodo->{'LAST-MODIFIED'}->getDateTime()->format(\DateTime::W3C), $this->tz); + $task['modified'] = $vtodo->{'LAST-MODIFIED'}->getDateTime()->format(\DateTime::ISO8601); } if (isset($vtodo->PRIORITY)) { @@ -80,7 +130,7 @@ class Taskwarrior implements IStorage { } if (isset($vtodo->DUE)){ - $task['due'] = new Carbon($vtodo->DUE->getDateTime()); + $task['due'] = $vtodo->DUE->getDateTime()->format(\DateTime::ISO8601); } if (isset($vtodo->RRULE)) { @@ -101,13 +151,13 @@ class Taskwarrior implements IStorage { case 'COMPLETED': $task['status'] = 'completed'; if (!isset($task['end'])) { - $task['end'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C), $this->tz); + $task['end'] = $vtodo->DTSTAMP->getDateTime()->format(\DateTime::ISO8601); } break; case 'CANCELED': $task['status'] = 'deleted'; if (!isset($task['end'])) { - $task['end'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C), $this->tz); + $task['end'] = $vtodo->DTSTAMP->getDateTime()->format(\DateTime::ISO8601); } break; } @@ -116,8 +166,8 @@ class Taskwarrior implements IStorage { if (isset($vtodo->CATEGORIES)) { $task['tags'] = []; foreach ($vtodo->CATEGORIES as $category) { - if (isset($this->configs['project_tag_suffix'])) { - $projTagSuffixRegExp = sprintf('/^%s/', $this->configs['project_tag_suffix']); + if (isset($this->configs['project_category_prefix'])) { + $projTagSuffixRegExp = sprintf('/^%s/', $this->configs['project_category_prefix']); if (preg_match($projTagSuffixRegExp, $category)) { $task['project'] = preg_replace($projTagSuffixRegExp, '', $category); continue; @@ -127,6 +177,15 @@ class Taskwarrior implements IStorage { } } + if (isset($vtodo->{'RELATED-TO'}) && isset($this->tasks[(string)$vtodo->{'RELATED-TO'}])) { + $relatedTask = $this->tasks[(string)$vtodo->{'RELATED-TO'}]; + $task['depends'] = $relatedTask['uuid']; + } + + if (isset($vtodo->GEO)){ + $task['geo'] = $vtodo->GEO->getRawMimeDirValue(); + } + return $task; } diff --git a/tests/Configs/ConfigTest.php b/tests/Configs/ConfigTest.php index cd094d3..983b482 100644 --- a/tests/Configs/ConfigTest.php +++ b/tests/Configs/ConfigTest.php @@ -26,8 +26,6 @@ class ConfigTest extends TestCase { $this->assertEquals($generalConfigs['logger']['level'], 'ERROR', 'ERROR is not set as default logger level'); $this->assertArrayHasKey('enabled', $generalConfigs['logger'], 'general config logger enabled property is missing'); $this->assertTrue($generalConfigs['logger']['enabled']); - $this->assertArrayHasKey('timezone', $generalConfigs, 'general config is missing timezone property'); - $this->assertEquals($generalConfigs['timezone'], 'UTC', 'UTC is not set as default timezone'); } public function testTaskwarriorConfig() { @@ -42,8 +40,8 @@ class ConfigTest extends TestCase { $this->assertEquals($taskwarriorConfigs['taskrc'], '/home/aerex/.taskrc'); $this->assertArrayHasKey('taskdata', $taskwarriorConfigs, 'taskwarrior config is missing taskdata property'); $this->assertEquals($taskwarriorConfigs['taskdata'], '/home/aerex/.task'); - $this->assertArrayHasKey('project_tag_suffix', $taskwarriorConfigs, 'taskwarrior config is missing project_tag_suffix property'); - $this->assertEquals($taskwarriorConfigs['project_tag_suffix'], 'project_'); + $this->assertArrayHasKey('project_category_prefix', $taskwarriorConfigs, 'taskwarrior config is missing project_category_prefix property'); + $this->assertEquals($taskwarriorConfigs['project_category_prefix'], 'project_'); } } diff --git a/tests/Configs/Fixtures/TaskwarriorConfig.yaml b/tests/Configs/Fixtures/TaskwarriorConfig.yaml index 3fca2dc..65f8f3f 100755 --- a/tests/Configs/Fixtures/TaskwarriorConfig.yaml +++ b/tests/Configs/Fixtures/TaskwarriorConfig.yaml @@ -2,9 +2,8 @@ general: logger: file: /home/aerex/baikal-storage-plugin.log level: DEBUG - timezone: 'America/Denver' storages: taskwarrior: taskdata: /home/aerex/.task taskrc: /home/aerex/.taskrc - project_tag_suffix: project_ + project_category_prefix: project_ diff --git a/tests/StorageManagerTest.php b/tests/StorageManagerTest.php index f5a727a..06191df 100644 --- a/tests/StorageManagerTest.php +++ b/tests/StorageManagerTest.php @@ -30,8 +30,7 @@ class StorageManagerTest extends TestCase { $this->mockLogger = $this->createMock(Logger::class); $this->configs = [ 'general' => [ - 'logger' => ['file' => '', 'level'=> 'DEBUG', 'enabled' => true], - 'timezone' => 'UTC' + 'logger' => ['file' => '', 'level'=> 'DEBUG', 'enabled' => true] ], 'storages' => [ 'taskwarrior' => ['taskrc' => '', 'taskdata' => ''] @@ -44,7 +43,7 @@ public function testAddTaskwarriorStorage() { $manager = new StorageManager($this->mockConfigBuilder); $manager->addStorage(Taskwarrior::NAME, $tw); $storages = $manager->getStorages(); - $configs = $manager->getConfigs(); + $this->configs = $manager->getConfigs(); $this->assertEquals(sizeof(array_keys($storages)), 1, 'Taskwarrior storage was not added'); $this->assertArrayHasKey('taskwarrior', $storages, 'Storages should have taskwarrior'); } diff --git a/tests/Storages/TaskwarriorTest.php b/tests/Storages/TaskwarriorTest.php index 1e165f7..9e922a3 100644 --- a/tests/Storages/TaskwarriorTest.php +++ b/tests/Storages/TaskwarriorTest.php @@ -24,7 +24,6 @@ class TaskwarriorTest extends TestCase { $configs = [ 'general' => [ 'logger' => ['file' => '', 'level'=> 'DEBUG', 'enabled' => true], - 'timezone' => 'UTC' ], 'storages' => [ 'taskwarrior' => ['taskrc' => '', 'taskdata' => ''] @@ -50,11 +49,47 @@ class TaskwarriorTest extends TestCase { $this->assertArrayHasKey('description', $task, 'task should have description'); $this->assertEquals((string)$vcalendar->VTODO->SUMMARY, $task['description']); $this->assertArrayHasKey('due', $task, 'task should have due'); - $this->assertEquals($vcalendar->VTODO->DUE->getDateTime(), $task['due']); + $this->assertEquals($vcalendar->VTODO->DUE->getDateTime()->format(\DateTime::ISO8601), $task['due']); $this->assertArrayHasKey('entry', $task, 'task should have an entry'); - $this->assertEquals($vcalendar->VTODO->DTSTAMP->getDateTime(), $task['entry']); + $this->assertEquals($vcalendar->VTODO->DTSTAMP->getDateTime()->format(\DateTime::ISO8601), $task['entry']); $this->assertArrayHasKey('start', $task, 'task should have start'); - $this->assertEquals($vcalendar->VTODO->DTSTART->getDateTime(), $task['start']); + $this->assertEquals($vcalendar->VTODO->DTSTART->getDateTime()->format(\DateTime::ISO8601), $task['start']); } +// public function testVObjectToTaskWithDifferentTimezone() { +// $configs = [ +// 'general' => [ +// 'logger' => ['file' => '', 'level'=> 'DEBUG', 'enabled' => true], +// ], +// 'storages' => [ +// 'taskwarrior' => ['taskrc' => '', 'taskdata' => ''] +// ] +// ]; +// $this->taskwarrior = new Taskwarrior($this->mockConsole, $configs, $this->mockLogger); +// $vcalendar = new Calendar([ +// 'VTODO' => [ +// 'SUMMARY' => 'Finish project', +// 'DTSTAMP' => new \DateTime('2020-07-04 10:00:00'), +// 'DTSTART' => new \DateTime('2020-07-04 12:00:00'), +// 'DTEND' => new \DateTime('2020-07-05 01:00:00'), +// 'DUE' => new \DateTime('2020-07-05 03:00:00'), +// 'LAST_MODIFIED' => new \DateTime('2020-07-04 13:00:00'), +// 'PRIORITY' => 5, +// 'RRULE' => 'FREQ=MONTHLY' +// ] +// ]); +// +// $task = $this->taskwarrior->vObjectToTask($vcalendar->VTODO); +// $this->assertArrayHasKey('uid', $task, 'task should have a uid'); +// $this->assertEquals((string)$vcalendar->VTODO->UID, $task['uid']); +// $this->assertArrayHasKey('description', $task, 'task should have description'); +// $this->assertEquals((string)$vcalendar->VTODO->SUMMARY, $task['description']); +// $this->assertArrayHasKey('due', $task, 'task should have due'); +// $this->assertEquals($vcalendar->VTODO->DUE->getDateTime(), $task['due']); +// $this->assertArrayHasKey('entry', $task, 'task should have an entry'); +// $this->assertEquals($vcalendar->VTODO->DTSTAMP->getDateTime(), $task['entry']); +// $this->assertArrayHasKey('start', $task, 'task should have start'); +// $this->assertEquals($vcalendar->VTODO->DTSTART->getDateTime(), $task['start']); +// +// } }