<?php 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; use Sabre\DAV\Exception\BadRequest; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Server; /** * The plugin to interact with Baikal and external storages * */ class Plugin extends ServerPlugin { /** * Reference to server object. * * @var Server */ protected $server; /** * @var StorageManager */ protected $storageManager; /** * @var $rawconfigs */ protected $rawConfigs; /** * Creates the Storage plugin * * @param CalendarProcessor $TWCalManager * */ function __construct($configFile){ $this->rawConfigs = $this->buildConfigurations($configFile); $this->storageManager = new StorageManager($this->rawConfigs); $this->logger = new Logger($this->rawConfigs, 'BaikalStorage'); $this->initializeStorages($this->rawConfigs); } private function getDisplayName($path) { // Remove filepath $urlParts = explode('/', $path); $calendarUrl = implode('/', array_slice($urlParts, 0, sizeof($urlParts)-1)); // Get displayname from collection $properties = $this->server->getProperties($calendarUrl, ['{DAV:}displayname']); return $properties['{DAV:}displayname'] ?? ''; } public function buildConfigurations($configFile) { $this->config = new ConfigBuilder($configFile); $this->config->add(new TaskwarriorConfig()); return $this->config->loadYaml(); } /** * Configure available storages in storage manager * */ public function initializeStorages($configs) { $taskwarrior = new Taskwarrior(new Console(['rc.verbose=nothing', 'rc.hooks=off', 'rc.confirmation=no']), $configs, new Logger($configs, 'Taskwarrior')); $this->storageManager->addStorage(Taskwarrior::NAME, $taskwarrior); } /** * Sets up the plugin * * @param Server $server * @return void */ function initialize(Server $server) { $this->server = $server; $server->on('beforeMethod:*', [$this, 'beforeMethod'], 15); } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using DAV\Server::getPlugin * * @return string */ function getPluginName() { return 'baikal-storage'; } /** * This method is called before any HTTP method handler. * * This method intercepts any GET, DELETE, PUT and PROPFIND. * * @param RequestInterface $request * @param ResponseInterface $response * * @return bool */ public function beforeMethod(RequestInterface $request, ResponseInterface $response) { switch ($request->getMethod()) { case 'PUT': $this->httpPut($request, $response); break; case 'POST': $this->httpPost($request, $response); break; case 'DELETE': $this->httpDelete($request); return; } } /** * This method handles the PUT method. * * @param RequestInterface $request * * @return bool */ function httpPut(RequestInterface $request){ $body = $request->getBodyAsString(); $vCal = \Sabre\VObject\Reader::read($body); $displayname = $this->getDisplayName($request->getPath()); try { if (!$this->storageManager->fromStorageSource($vCal)) { $this->storageManager->import($vCal, $displayname); } } catch(BadRequest $e){ throw new BadRequest($e->getMessage(), null, $e); } catch(\Exception $e){ throw new \Exception($e->getMessage(), null, $e); } $request->setBody($body); } /** * This method handles the POST method. * * @param RequestInterface $request * */ 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); } /** * This method handles the DELETE method. * * @param RequestInterface $request * @param ResponseInterface $response * */ public function httpDelete(RequestInterface $request) { try { $body = $request->getBodyAsString(); $path = $request->getPath(); $paths = explode('/', $path); if (sizeof($paths) > 1) { $uid = str_replace('.ics', '', $paths[sizeof($paths)-1]); // Attempt to delete if we are removing an ics file if ($uid != '') { $this->storageManager->remove($uid); } } } catch(BadRequest $e){ throw new BadRequest($e->getMessage(), null, $e); } catch(\Exception $e){ throw new \Exception($e->getMessage(), null, $e); } $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 = '<form method="post" action="">'; $html .= '<section><h1>Configuration - Baikal Storage</h1>'; $html .= '<section><h2>general</h2>'; $html .= '<table class="propTable">'; $html .= '<tr>'; $html .= '<th>log level</th>'; $html .= '<td>The minimum log level </td>'; $html .= '<td>'; $html .= '<select name="logLevel">'; foreach (Monolog::getLevels() as $key => $value) { if ($key == $configuredLogLevel) { $selected = ' selected '; } else { $selected = ''; } $html .= '<option value="'. $key .'"' . $selected . '>'. $key .'</option>'; } $html .= '</select>'; $html .= '</tr>'; $html .= '<tr>'; $html .= '<th>log file path</th>'; $html .= '<td>The absolute file path of the log</td>'; $html .= '<td><input name="logFilePath" placeholder="/opt/baikal/log" value='. $logFilePath . ' type="text" id="logFilePath"></input></td>'; $html .= '</tr>'; $html .= '<tr>'; $html .= '</table>'; $html .= '</section>'; 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 .= '<section>'; $html .= '<h2>' . $storage::NAME . '</h2>'; $html .= '<table class="propTable">'; $html .= '<input type="hidden" name="baikalStorageAction" value="saveConfigs"></input>'; $html .= '<input type="hidden" name="baikalStorage" value="taskwarrior"></input>'; $html .= $storage->getConfigBrowser(); $html .= '</table>'; $html .= '</section>'; $html .= '<input type="submit" value="save"></input>'; $html .= '</form>'; } return $html; } /** * Returns a bunch of meta-data about the plugin. * * Providing this information is optional, and is mainly displayed by the * Browser plugin. * * The description key in the returned array may contain html and will not * be sanitized. * * @return array */ function getPluginInfo() { return [ 'name' => $this->getPluginName(), 'description' => 'The plugin provides synchronization between remote storages and iCal todo events', 'link' => 'https://git.aerex.me/Aerex/baikal-storage-plugin', 'config' => true ]; } }