1<?php 2/* 3 * Yurii's Gantt Plugin 4 * 5 * Copyright (C) 2020 Yurii K. 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see http://www.gnu.org/licenses 19 */ 20 21namespace dokuwiki\plugin\yuriigantt\src\Driver; 22 23use dokuwiki\plugin\yuriigantt\src\Driver\Embedded\Lexer; 24use dokuwiki\plugin\yuriigantt\src\Driver\Embedded\Handler; 25use dokuwiki\plugin\yuriigantt\src\Driver\Embedded\Renderer; 26use dokuwiki\plugin\yuriigantt\src\Entities\Link; 27use dokuwiki\plugin\yuriigantt\src\Entities\Task; 28 29class Embedded implements DriverInterface 30{ 31 const DSN = ':embedded:'; 32 const MODE = 'embedded'; 33 34 /** @var string */ 35 protected $pageId; 36 /** @var Handler */ 37 protected $handler; 38 /** @var Lexer */ 39 protected $lexer; 40 /** @var bool */ 41 protected $isOpen = false; 42 43 44 /** 45 * Open DB connection for the page 46 * @param string $pageId 47 * @throws \Exception 48 */ 49 public function open($pageId) 50 { 51 if ($this->isOpen && $this->pageId === $pageId) { 52 return; 53 } 54 55 if ($this->isOpen && $this->pageId !== $pageId) { 56 throw new \Exception('Already open for another page! Close first!'); 57 } 58 59 $this->handler = new Handler(); 60 $this->lexer = new Lexer($this->handler, self::MODE); 61 Embedded::addLexerPattern($this->lexer, self::MODE); 62 63 $rawPage = rawWiki($pageId); 64 $rawPage = $rawPage === false ? false : $this->lexer->parse($rawPage); 65 66 if (!$rawPage) { 67 throw new \Exception('Failed to open dataset for page ' . $pageId); 68 } 69 70 $this->pageId = $pageId; 71 $this->isOpen = true; 72 } 73 74 75 /** 76 * Close connection 77 */ 78 public function close() 79 { 80 $this->handler = $this->lexer = $this->pageId = null; 81 $this->isOpen = false; 82 } 83 84 85 protected function checkOpen() 86 { 87 if (!$this->isOpen) { 88 throw new \Exception("Database MUST BE open first!"); 89 } 90 } 91 92 93 public static function emptyDatabase() 94 { 95 return <<<TXT 96~~~~GANTT~~~~ 97 98~~~~~~~~~~~ 99TXT; 100 } 101 102 103 public static function initDatabase($pageId) 104 { 105 $database = (object)[ 106 'pageId' => $pageId, 107 'version' => '1.0', 108 'dsn' => Embedded::DSN, 109 'increment' => [ 110 'task' => 1, 111 'link' => 1, 112 ], 113 'gantt' => [ 114 'data' => [], 115 'links' => [], 116 ] 117 ]; 118 119 if ($rawPage = io_readFile(wikiFN($pageId))) { 120 $rawPage = str_replace(self::emptyDatabase(), self::embed($database), $rawPage); 121 io_saveFile(wikiFN($pageId), $rawPage); 122 } 123 124 return $database; 125 } 126 127 128 protected function flush() 129 { 130 $this->checkOpen(); 131 $renderer = new Renderer(); 132 133 foreach ($this->handler->calls as $instruction) { 134 call_user_func_array([&$renderer, $instruction[0]], $instruction[1] ? $instruction[1] : []); 135 } 136 137 //file_put_contents(__DIR__ . '/test_page_output.txt', $renderer->doc); 138 io_saveFile(wikiFN($this->pageId), $renderer->doc); 139 } 140 141 142 /** 143 * Returns code ready for embedding into wiki Page 144 * 145 * @param \stdClass $database 146 * @return string 147 */ 148 public static function embed(\stdClass $database) 149 { 150 $embedded = json_encode($database, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); 151 152 return <<<CODE 153~~~~GANTT~~~~ 154$embedded 155~~~~~~~~~~~ 156CODE; 157 } 158 159 /** 160 * @param Lexer $lexer 161 * @param string $mode 162 */ 163 public static function addLexerPattern($lexer, $mode) 164 { 165 $lexer->addSpecialPattern('~~~~GANTT~~~~\n.*?\n~~~~~~~~~~~', $mode, 'plugin_yuriigantt'); 166 } 167 168 169 protected function getDatabase() 170 { 171 $this->checkOpen(); 172 return $this->handler->getDatabase(); 173 } 174 175 176 /** 177 * {@inheritdoc} 178 */ 179 public function updateLink(Link $link) 180 { 181 $this->checkOpen(); 182 $database =& $this->getDatabase(); 183 $links =& $database->gantt->links; 184 185 for ($i = 0; $i < count($links); $i++) { 186 if ($links[$i]->id == $link->id) { 187 $links[$i] = $link; 188 break; 189 } 190 } 191 192 $this->flush(); 193 194 return $link; 195 } 196 197 198 /** 199 * {@inheritdoc} 200 */ 201 public function deleteLink($id) 202 { 203 $this->checkOpen(); 204 $database =& $this->getDatabase(); 205 $links =& $database->gantt->links; 206 207 for ($i = 0; $i < count($links); $i++) { 208 if ($links[$i]->id == $id) { 209 unset($links[$i]); 210 $links = array_values($links); 211 break; 212 } 213 } 214 215 $this->flush(); 216 } 217 218 219 /** 220 * {@inheritdoc} 221 */ 222 public function addLink(Link $link) 223 { 224 $this->checkOpen(); 225 $database =& $this->getDatabase(); 226 $link->id = $database->increment->link++; 227 $database->gantt->links[] = $link; 228 229 $this->flush(); 230 231 return $link; 232 } 233 234 235 /** 236 * {@inheritdoc} 237 */ 238 public function updateTask(Task $task) 239 { 240 $this->checkOpen(); 241 $database =& $this->getDatabase(); 242 $tasks =& $database->gantt->data; 243 244 for ($i = 0; $i < count($tasks); $i++) { 245 if ($tasks[$i]->id == $task->id) { 246 $tasks[$i] = $task; 247 break; 248 } 249 } 250 251 $this->flush(); 252 253 return $task; 254 } 255 256 257 /** 258 * {@inheritdoc} 259 */ 260 public function deleteTask($id) 261 { 262 $this->checkOpen(); 263 $database =& $this->getDatabase(); 264 $tasks =& $database->gantt->data; 265 $tasks = array_column($tasks, null, 'id'); //re-index by ID 266 $links =& $database->gantt->links; 267 268 $deleteLinks = function ($taskId) use (&$links) { 269 /** @var Link[] $links */ 270 foreach ($links as &$link) { 271 if (in_array($taskId, [$link->source, $link->target])) { 272 $link = null; 273 } 274 } 275 276 $links = array_values(array_filter($links)); 277 }; 278 279 $deleteChildren = function ($parentId) use (&$tasks, &$deleteChildren, $deleteLinks) { 280 foreach ($tasks as &$task) { 281 if ($task->parent == $parentId) { 282 $deleteChildren($task->id); 283 $deleteLinks($task->id); 284 $task = null; 285 } 286 } 287 }; 288 289 $deleteChildren($id); 290 $tasks[$id] = null; 291 $tasks = array_values(array_filter($tasks)); 292 $deleteLinks($id); 293 294 $this->flush(); 295 } 296 297 298 /** 299 * {@inheritdoc} 300 */ 301 public function addTask(Task $task) 302 { 303 $this->checkOpen(); 304 $database =& $this->getDatabase(); 305 $task->id = $database->increment->task++; 306 $database->gantt->data[] = $task; 307 308 $this->flush(); 309 310 return $task; 311 } 312 313} 314