14d6d17d0SAndreas Gohr<?php 2c6d8c1d9SAndreas Gohr 3fea1a86fSAndreas Gohruse dokuwiki\ErrorHandler; 4c6d8c1d9SAndreas Gohruse dokuwiki\Extension\AuthPlugin; 5fea1a86fSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 6c6d8c1d9SAndreas Gohr 74d6d17d0SAndreas Gohr/** 84d6d17d0SAndreas Gohr * DokuWiki Plugin acknowledge (Helper Component) 94d6d17d0SAndreas Gohr * 104d6d17d0SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 114d6d17d0SAndreas Gohr * @author Andreas Gohr, Anna Dabrowska <dokuwiki@cosmocode.de> 124d6d17d0SAndreas Gohr */ 134d6d17d0SAndreas Gohrclass helper_plugin_acknowledge extends DokuWiki_Plugin 144d6d17d0SAndreas Gohr{ 154d6d17d0SAndreas Gohr 16fea1a86fSAndreas Gohr protected $db; 17fea1a86fSAndreas Gohr 18639d4c50SAndreas Gohr // region Database Management 19639d4c50SAndreas Gohr 20cabb51d3SAndreas Gohr /** 21fea1a86fSAndreas Gohr * Get SQLiteDB instance 22fea1a86fSAndreas Gohr * 23fea1a86fSAndreas Gohr * @return SQLiteDB|null 24cabb51d3SAndreas Gohr */ 25cabb51d3SAndreas Gohr public function getDB() 26cabb51d3SAndreas Gohr { 27fea1a86fSAndreas Gohr if ($this->db === null) { 28fea1a86fSAndreas Gohr try { 29fea1a86fSAndreas Gohr $this->db = new SQLiteDB('acknowledgement', __DIR__ . '/db'); 30fea1a86fSAndreas Gohr 31fea1a86fSAndreas Gohr // register our custom functions 32fea1a86fSAndreas Gohr $this->db->getPdo()->sqliteCreateFunction('AUTH_ISMEMBER', [$this, 'auth_isMember'], -1); 33fea1a86fSAndreas Gohr $this->db->getPdo()->sqliteCreateFunction('MATCHES_PAGE_PATTERN', [$this, 'matchPagePattern'], 2); 34fea1a86fSAndreas Gohr } catch (\Exception $exception) { 35fea1a86fSAndreas Gohr if (defined('DOKU_UNITTEST')) throw new \RuntimeException('Could not load SQLite', 0, $exception); 36fea1a86fSAndreas Gohr ErrorHandler::logException($exception); 37cabb51d3SAndreas Gohr msg($this->getLang('error sqlite plugin missing'), -1); 38cabb51d3SAndreas Gohr return null; 39cabb51d3SAndreas Gohr } 40cabb51d3SAndreas Gohr } 41fea1a86fSAndreas Gohr return $this->db; 429c3eae1eSAnna Dabrowska } 439c3eae1eSAnna Dabrowska 449c3eae1eSAnna Dabrowska /** 459c3eae1eSAnna Dabrowska * Wrapper function for auth_isMember which accepts groups as string 469c3eae1eSAnna Dabrowska * 479c3eae1eSAnna Dabrowska * @param string $memberList 489c3eae1eSAnna Dabrowska * @param string $user 499c3eae1eSAnna Dabrowska * @param string $groups 509c3eae1eSAnna Dabrowska * @return bool 519c3eae1eSAnna Dabrowska */ 529c3eae1eSAnna Dabrowska public function auth_isMember($memberList, $user, $groups) 539c3eae1eSAnna Dabrowska { 5495113ed8SAnna Dabrowska return auth_isMember($memberList, $user, explode('///', $groups)); 559c3eae1eSAnna Dabrowska } 569c3eae1eSAnna Dabrowska 579c3eae1eSAnna Dabrowska /** 58639d4c50SAndreas Gohr * Fills the page index with all unknown pages from the fulltext index 59639d4c50SAndreas Gohr * @return void 60639d4c50SAndreas Gohr */ 61639d4c50SAndreas Gohr public function updatePageIndex() 62639d4c50SAndreas Gohr { 63639d4c50SAndreas Gohr $sqlite = $this->getDB(); 64639d4c50SAndreas Gohr if (!$sqlite) return; 65639d4c50SAndreas Gohr 66639d4c50SAndreas Gohr $pages = idx_getIndex('page', ''); 67639d4c50SAndreas Gohr $sql = "INSERT OR IGNORE INTO pages (page, lastmod) VALUES (?,?)"; 68639d4c50SAndreas Gohr 69fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 70639d4c50SAndreas Gohr foreach ($pages as $page) { 71639d4c50SAndreas Gohr $page = trim($page); 72639d4c50SAndreas Gohr $lastmod = @filemtime(wikiFN($page)); 73639d4c50SAndreas Gohr if ($lastmod) { 74fea1a86fSAndreas Gohr try { 75fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 76fea1a86fSAndreas Gohr } catch (\Exception $exception) { 77fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 78fea1a86fSAndreas Gohr throw $exception; 79639d4c50SAndreas Gohr } 80639d4c50SAndreas Gohr } 81fea1a86fSAndreas Gohr } 82fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 83639d4c50SAndreas Gohr } 84639d4c50SAndreas Gohr 85639d4c50SAndreas Gohr /** 86639d4c50SAndreas Gohr * Check if the given pattern matches the given page 87639d4c50SAndreas Gohr * 88639d4c50SAndreas Gohr * @param string $pattern the pattern to check against 89639d4c50SAndreas Gohr * @param string $page the cleaned pageid to check 90639d4c50SAndreas Gohr * @return bool 91639d4c50SAndreas Gohr */ 92639d4c50SAndreas Gohr public function matchPagePattern($pattern, $page) 93639d4c50SAndreas Gohr { 94639d4c50SAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 95639d4c50SAndreas Gohr 96639d4c50SAndreas Gohr // regex patterns 97639d4c50SAndreas Gohr if ($pattern[0] == '/') { 98639d4c50SAndreas Gohr return (bool)preg_match($pattern, ":$page"); 99639d4c50SAndreas Gohr } 100639d4c50SAndreas Gohr 101639d4c50SAndreas Gohr $pns = ':' . getNS($page) . ':'; 102639d4c50SAndreas Gohr 103639d4c50SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 104639d4c50SAndreas Gohr if (substr($pattern, -2) == '**') { 105639d4c50SAndreas Gohr // upper namespaces match 106639d4c50SAndreas Gohr if (strpos($pns, $ans) === 0) { 107639d4c50SAndreas Gohr return true; 108639d4c50SAndreas Gohr } 109639d4c50SAndreas Gohr } elseif (substr($pattern, -1) == '*') { 110639d4c50SAndreas Gohr // namespaces match exact 111639d4c50SAndreas Gohr if ($ans == $pns) { 112639d4c50SAndreas Gohr return true; 113639d4c50SAndreas Gohr } 114639d4c50SAndreas Gohr } else { 115639d4c50SAndreas Gohr // exact match 116639d4c50SAndreas Gohr if (cleanID($pattern) == $page) { 117639d4c50SAndreas Gohr return true; 118639d4c50SAndreas Gohr } 119639d4c50SAndreas Gohr } 120639d4c50SAndreas Gohr 121639d4c50SAndreas Gohr return false; 122639d4c50SAndreas Gohr } 123639d4c50SAndreas Gohr 124639d4c50SAndreas Gohr // endregion 125639d4c50SAndreas Gohr // region Page Data 126639d4c50SAndreas Gohr 127639d4c50SAndreas Gohr /** 128ef3ab392SAndreas Gohr * Delete a page 129ef3ab392SAndreas Gohr * 130ef3ab392SAndreas Gohr * Cascades to delete all assigned data, etc. 131ef3ab392SAndreas Gohr * 132ef3ab392SAndreas Gohr * @param string $page Page ID 133ef3ab392SAndreas Gohr */ 134ef3ab392SAndreas Gohr public function removePage($page) 135ef3ab392SAndreas Gohr { 136ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 137ef3ab392SAndreas Gohr if (!$sqlite) return; 138ef3ab392SAndreas Gohr 139ef3ab392SAndreas Gohr $sql = "DELETE FROM pages WHERE page = ?"; 140fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 141ef3ab392SAndreas Gohr } 142ef3ab392SAndreas Gohr 143ef3ab392SAndreas Gohr /** 1445dee13f7SAnna Dabrowska * Update last modified date of page if content has changed 145ef3ab392SAndreas Gohr * 146ef3ab392SAndreas Gohr * @param string $page Page ID 147ef3ab392SAndreas Gohr * @param int $lastmod timestamp of last non-minor change 148ef3ab392SAndreas Gohr */ 1495dee13f7SAnna Dabrowska public function storePageDate($page, $lastmod, $newContent) 150ef3ab392SAndreas Gohr { 151ed4e8871SAnna Dabrowska $changelog = new \dokuwiki\ChangeLog\PageChangeLog($page); 152789aa26fSAnna Dabrowska $revs = $changelog->getRevisions(0, 1); 153ed4e8871SAnna Dabrowska 154ed4e8871SAnna Dabrowska // compare content 155ed4e8871SAnna Dabrowska $oldContent = str_replace(NL, '', io_readFile(wikiFN($page, $revs[0]))); 156ed4e8871SAnna Dabrowska $newContent = str_replace(NL, '', $newContent); 157ed4e8871SAnna Dabrowska if ($oldContent === $newContent) return; 158ed4e8871SAnna Dabrowska 159ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 160ef3ab392SAndreas Gohr if (!$sqlite) return; 161ef3ab392SAndreas Gohr 162ef3ab392SAndreas Gohr $sql = "REPLACE INTO pages (page, lastmod) VALUES (?,?)"; 163fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 164ef3ab392SAndreas Gohr } 165ef3ab392SAndreas Gohr 166639d4c50SAndreas Gohr // endregion 167639d4c50SAndreas Gohr // region Assignments 168639d4c50SAndreas Gohr 169ef3ab392SAndreas Gohr /** 170f09444ffSAndreas Gohr * Clears direct assignments for a page 171f09444ffSAndreas Gohr * 172cabb51d3SAndreas Gohr * @param string $page Page ID 173cabb51d3SAndreas Gohr */ 174f09444ffSAndreas Gohr public function clearPageAssignments($page) 175cabb51d3SAndreas Gohr { 176cabb51d3SAndreas Gohr $sqlite = $this->getDB(); 177cabb51d3SAndreas Gohr if (!$sqlite) return; 178cabb51d3SAndreas Gohr 179f09444ffSAndreas Gohr $sql = "UPDATE assignments SET pageassignees = '' WHERE page = ?"; 180fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 181f09444ffSAndreas Gohr } 182f09444ffSAndreas Gohr 183f09444ffSAndreas Gohr /** 184639d4c50SAndreas Gohr * Set assignees for a given page as manually specified 185639d4c50SAndreas Gohr * 186639d4c50SAndreas Gohr * @param string $page Page ID 187639d4c50SAndreas Gohr * @param string $assignees 188639d4c50SAndreas Gohr * @return void 189639d4c50SAndreas Gohr */ 190639d4c50SAndreas Gohr public function setPageAssignees($page, $assignees) 191639d4c50SAndreas Gohr { 192639d4c50SAndreas Gohr $sqlite = $this->getDB(); 193639d4c50SAndreas Gohr if (!$sqlite) return; 194639d4c50SAndreas Gohr 195639d4c50SAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 196639d4c50SAndreas Gohr 197639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'pageassignees') VALUES (?,?)"; 198fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 199639d4c50SAndreas Gohr } 200639d4c50SAndreas Gohr 201639d4c50SAndreas Gohr /** 202639d4c50SAndreas Gohr * Set assignees for a given page from the patterns 203639d4c50SAndreas Gohr * @param string $page Page ID 204639d4c50SAndreas Gohr */ 205639d4c50SAndreas Gohr public function setAutoAssignees($page) 206639d4c50SAndreas Gohr { 207639d4c50SAndreas Gohr $sqlite = $this->getDB(); 208639d4c50SAndreas Gohr if (!$sqlite) return; 209639d4c50SAndreas Gohr 210639d4c50SAndreas Gohr $patterns = $this->getAssignmentPatterns(); 211639d4c50SAndreas Gohr 212639d4c50SAndreas Gohr // given assignees 213639d4c50SAndreas Gohr $assignees = ''; 214639d4c50SAndreas Gohr 215639d4c50SAndreas Gohr // find all patterns that match the page and add the configured assignees 216639d4c50SAndreas Gohr foreach ($patterns as $pattern => $assignees) { 217639d4c50SAndreas Gohr if ($this->matchPagePattern($pattern, $page)) { 218639d4c50SAndreas Gohr $assignees .= ',' . $assignees; 219639d4c50SAndreas Gohr } 220639d4c50SAndreas Gohr } 221639d4c50SAndreas Gohr 222639d4c50SAndreas Gohr // remove duplicates and empty entries 223639d4c50SAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 224639d4c50SAndreas Gohr 225639d4c50SAndreas Gohr // store the assignees 226639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'autoassignees') VALUES (?,?)"; 227fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 228639d4c50SAndreas Gohr } 229639d4c50SAndreas Gohr 230639d4c50SAndreas Gohr /** 231639d4c50SAndreas Gohr * Is the given user one of the assignees for this page 232639d4c50SAndreas Gohr * 233639d4c50SAndreas Gohr * @param string $page Page ID 234639d4c50SAndreas Gohr * @param string $user user name to check 235639d4c50SAndreas Gohr * @param string[] $groups groups this user is in 236639d4c50SAndreas Gohr * @return bool 237639d4c50SAndreas Gohr */ 238639d4c50SAndreas Gohr public function isUserAssigned($page, $user, $groups) 239639d4c50SAndreas Gohr { 240639d4c50SAndreas Gohr $sqlite = $this->getDB(); 241639d4c50SAndreas Gohr if (!$sqlite) return false; 242639d4c50SAndreas Gohr 243639d4c50SAndreas Gohr $sql = "SELECT pageassignees,autoassignees FROM assignments WHERE page = ?"; 244fea1a86fSAndreas Gohr $record = $sqlite->queryRecord($sql, $page); 245*a806aa3dSSven if (!$record) return false; 246fea1a86fSAndreas Gohr $assignees = $record['pageassignees'] . ',' . $record['autoassignees']; 247639d4c50SAndreas Gohr return auth_isMember($assignees, $user, $groups); 248639d4c50SAndreas Gohr } 249639d4c50SAndreas Gohr 250639d4c50SAndreas Gohr /** 251639d4c50SAndreas Gohr * Fetch all assignments for a given user, with additional page information, 252639d4c50SAndreas Gohr * filtering already granted acknowledgements. 253639d4c50SAndreas Gohr * 254639d4c50SAndreas Gohr * @param string $user 255639d4c50SAndreas Gohr * @param array $groups 256639d4c50SAndreas Gohr * @return array|bool 257639d4c50SAndreas Gohr */ 258639d4c50SAndreas Gohr public function getUserAssignments($user, $groups) 259639d4c50SAndreas Gohr { 260639d4c50SAndreas Gohr $sqlite = $this->getDB(); 261639d4c50SAndreas Gohr if (!$sqlite) return false; 262639d4c50SAndreas Gohr 263639d4c50SAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, C.ack FROM assignments A 264639d4c50SAndreas Gohr JOIN pages B 265639d4c50SAndreas Gohr ON A.page = B.page 266639d4c50SAndreas Gohr LEFT JOIN acks C 267639d4c50SAndreas Gohr ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) ) 268639d4c50SAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees , ? , ?) 269639d4c50SAndreas Gohr AND ack IS NULL"; 270639d4c50SAndreas Gohr 271fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, $user, $user, implode('///', $groups)); 272639d4c50SAndreas Gohr } 273639d4c50SAndreas Gohr 274639d4c50SAndreas Gohr 275639d4c50SAndreas Gohr /** 276639d4c50SAndreas Gohr * Resolve names of users assigned to a given page 277639d4c50SAndreas Gohr * 278639d4c50SAndreas Gohr * This can be slow on huge user bases! 279639d4c50SAndreas Gohr * 280639d4c50SAndreas Gohr * @param string $page 281639d4c50SAndreas Gohr * @return array|false 282639d4c50SAndreas Gohr */ 283639d4c50SAndreas Gohr public function getPageAssignees($page) 284639d4c50SAndreas Gohr { 285639d4c50SAndreas Gohr $sqlite = $this->getDB(); 286639d4c50SAndreas Gohr if (!$sqlite) return false; 287639d4c50SAndreas Gohr /** @var AuthPlugin $auth */ 288639d4c50SAndreas Gohr global $auth; 289639d4c50SAndreas Gohr 290639d4c50SAndreas Gohr $sql = "SELECT pageassignees || ',' || autoassignees AS 'assignments' 291639d4c50SAndreas Gohr FROM assignments 292639d4c50SAndreas Gohr WHERE page = ?"; 293fea1a86fSAndreas Gohr $assignments = $sqlite->queryValue($sql, $page); 294639d4c50SAndreas Gohr 295639d4c50SAndreas Gohr $users = []; 296639d4c50SAndreas Gohr foreach (explode(',', $assignments) as $item) { 297639d4c50SAndreas Gohr $item = trim($item); 298639d4c50SAndreas Gohr if ($item === '') continue; 299639d4c50SAndreas Gohr if ($item[0] == '@') { 300639d4c50SAndreas Gohr $users = array_merge( 301639d4c50SAndreas Gohr $users, 302639d4c50SAndreas Gohr array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)])) 303639d4c50SAndreas Gohr ); 304639d4c50SAndreas Gohr } else { 305639d4c50SAndreas Gohr $users[] = $item; 306639d4c50SAndreas Gohr } 307639d4c50SAndreas Gohr } 308639d4c50SAndreas Gohr 309639d4c50SAndreas Gohr return array_unique($users); 310639d4c50SAndreas Gohr } 311639d4c50SAndreas Gohr 312639d4c50SAndreas Gohr // endregion 313639d4c50SAndreas Gohr // region Assignment Patterns 314639d4c50SAndreas Gohr 315639d4c50SAndreas Gohr /** 316f09444ffSAndreas Gohr * Get all the assignment patterns 317f09444ffSAndreas Gohr * @return array (pattern => assignees) 318f09444ffSAndreas Gohr */ 319f09444ffSAndreas Gohr public function getAssignmentPatterns() 320f09444ffSAndreas Gohr { 321f09444ffSAndreas Gohr $sqlite = $this->getDB(); 322f09444ffSAndreas Gohr if (!$sqlite) return []; 323f09444ffSAndreas Gohr 324f09444ffSAndreas Gohr $sql = "SELECT pattern, assignees FROM assignments_patterns"; 325fea1a86fSAndreas Gohr return $sqlite->queryKeyValueList($sql); 326f09444ffSAndreas Gohr } 327f09444ffSAndreas Gohr 328f09444ffSAndreas Gohr /** 329f09444ffSAndreas Gohr * Save new assignment patterns 330f09444ffSAndreas Gohr * 331f09444ffSAndreas Gohr * This resaves all patterns and reapplies them 332f09444ffSAndreas Gohr * 333f09444ffSAndreas Gohr * @param array $patterns (pattern => assignees) 334f09444ffSAndreas Gohr */ 335639d4c50SAndreas Gohr public function saveAssignmentPatterns($patterns) 336639d4c50SAndreas Gohr { 337f09444ffSAndreas Gohr $sqlite = $this->getDB(); 338f09444ffSAndreas Gohr if (!$sqlite) return; 339f09444ffSAndreas Gohr 340fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 341fea1a86fSAndreas Gohr try { 342f09444ffSAndreas Gohr 343fea1a86fSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all assignments */ 344f09444ffSAndreas Gohr $sql = "UPDATE assignments SET autoassignees = ''"; 345fea1a86fSAndreas Gohr $sqlite->exec($sql); 346f09444ffSAndreas Gohr 347f09444ffSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all patterns */ 348f09444ffSAndreas Gohr $sql = "DELETE FROM assignments_patterns"; 349fea1a86fSAndreas Gohr $sqlite->exec($sql); 350f09444ffSAndreas Gohr 351f09444ffSAndreas Gohr // insert new patterns and gather affected pages 352f09444ffSAndreas Gohr $pages = []; 353f09444ffSAndreas Gohr 354f09444ffSAndreas Gohr $sql = "REPLACE INTO assignments_patterns (pattern, assignees) VALUES (?,?)"; 355f09444ffSAndreas Gohr foreach ($patterns as $pattern => $assignees) { 356f09444ffSAndreas Gohr $pattern = trim($pattern); 357f09444ffSAndreas Gohr $assignees = trim($assignees); 358f09444ffSAndreas Gohr if (!$pattern || !$assignees) continue; 359fea1a86fSAndreas Gohr $sqlite->exec($sql, [$pattern, $assignees]); 360f09444ffSAndreas Gohr 361f09444ffSAndreas Gohr // patterns may overlap, so we need to gather all affected pages first 362f09444ffSAndreas Gohr $affectedPages = $this->getPagesMatchingPattern($pattern); 363f09444ffSAndreas Gohr foreach ($affectedPages as $page) { 364f09444ffSAndreas Gohr if (isset($pages[$page])) { 365f09444ffSAndreas Gohr $pages[$page] .= ',' . $assignees; 366f09444ffSAndreas Gohr } else { 367f09444ffSAndreas Gohr $pages[$page] = $assignees; 368f09444ffSAndreas Gohr } 369f09444ffSAndreas Gohr } 370f09444ffSAndreas Gohr } 371f09444ffSAndreas Gohr 372f09444ffSAndreas Gohr $sql = "INSERT INTO assignments (page, autoassignees) VALUES (?, ?) 373f09444ffSAndreas Gohr ON CONFLICT(page) 374f09444ffSAndreas Gohr DO UPDATE SET autoassignees = ?"; 375f09444ffSAndreas Gohr foreach ($pages as $page => $assignees) { 376f09444ffSAndreas Gohr // remove duplicates and empty entries 377f09444ffSAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 378fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees, $assignees]); 379f09444ffSAndreas Gohr } 380fea1a86fSAndreas Gohr } catch (Exception $e) { 381fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 382fea1a86fSAndreas Gohr throw $e; 383fea1a86fSAndreas Gohr } 384fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 385f09444ffSAndreas Gohr } 386f09444ffSAndreas Gohr 387f09444ffSAndreas Gohr /** 388f09444ffSAndreas Gohr * Get all known pages that match the given pattern 389f09444ffSAndreas Gohr * 390f09444ffSAndreas Gohr * @param $pattern 391f09444ffSAndreas Gohr * @return string[] 392f09444ffSAndreas Gohr */ 393639d4c50SAndreas Gohr public function getPagesMatchingPattern($pattern) 394639d4c50SAndreas Gohr { 395f09444ffSAndreas Gohr $sqlite = $this->getDB(); 396f09444ffSAndreas Gohr if (!$sqlite) return []; 397f09444ffSAndreas Gohr 398f09444ffSAndreas Gohr $sql = "SELECT page FROM pages WHERE MATCHES_PAGE_PATTERN(?, page)"; 399fea1a86fSAndreas Gohr $pages = $sqlite->queryAll($sql, $pattern); 400f09444ffSAndreas Gohr 401f09444ffSAndreas Gohr return array_column($pages, 'page'); 402f09444ffSAndreas Gohr } 403f09444ffSAndreas Gohr 404639d4c50SAndreas Gohr // endregion 405639d4c50SAndreas Gohr // region Acknowledgements 406ef3ab392SAndreas Gohr 407ef3ab392SAndreas Gohr /** 408ef3ab392SAndreas Gohr * Has the given user acknowledged the given page? 409ef3ab392SAndreas Gohr * 410ef3ab392SAndreas Gohr * @param string $page 411ef3ab392SAndreas Gohr * @param string $user 4125773dd37SAnna Dabrowska * @return bool|int timestamp of acknowledgement or false 413ef3ab392SAndreas Gohr */ 414ef3ab392SAndreas Gohr public function hasUserAcknowledged($page, $user) 415ef3ab392SAndreas Gohr { 416ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 417ef3ab392SAndreas Gohr if (!$sqlite) return false; 418ef3ab392SAndreas Gohr 419ef3ab392SAndreas Gohr $sql = "SELECT ack 420ef3ab392SAndreas Gohr FROM acks A, pages B 421ef3ab392SAndreas Gohr WHERE A.page = B.page 4225773dd37SAnna Dabrowska AND A.page = ? 4235773dd37SAnna Dabrowska AND A.user = ? 424ef3ab392SAndreas Gohr AND A.ack >= B.lastmod"; 425ef3ab392SAndreas Gohr 426fea1a86fSAndreas Gohr $acktime = $sqlite->queryValue($sql, $page, $user); 427ef3ab392SAndreas Gohr 428ef3ab392SAndreas Gohr return $acktime ? (int)$acktime : false; 429ef3ab392SAndreas Gohr } 4305773dd37SAnna Dabrowska 4315773dd37SAnna Dabrowska /** 432d9a8334dSAnna Dabrowska * Timestamp of the latest acknowledgment of the given page 433d9a8334dSAnna Dabrowska * by the given user 434d9a8334dSAnna Dabrowska * 435d9a8334dSAnna Dabrowska * @param string $page 436d9a8334dSAnna Dabrowska * @param string $user 437d9a8334dSAnna Dabrowska * @return bool|string 438d9a8334dSAnna Dabrowska */ 439d9a8334dSAnna Dabrowska public function getLatestUserAcknowledgement($page, $user) 440d9a8334dSAnna Dabrowska { 441d9a8334dSAnna Dabrowska $sqlite = $this->getDB(); 442d9a8334dSAnna Dabrowska if (!$sqlite) return false; 443d9a8334dSAnna Dabrowska 444d9a8334dSAnna Dabrowska $sql = "SELECT MAX(ack) 445d9a8334dSAnna Dabrowska FROM acks 446d9a8334dSAnna Dabrowska WHERE page = ? 447d9a8334dSAnna Dabrowska AND user = ?"; 448d9a8334dSAnna Dabrowska 449fea1a86fSAndreas Gohr return $sqlite->queryValue($sql, [$page, $user]); 450d9a8334dSAnna Dabrowska } 451d9a8334dSAnna Dabrowska 452d9a8334dSAnna Dabrowska /** 4535773dd37SAnna Dabrowska * Save user's acknowledgement for a given page 4545773dd37SAnna Dabrowska * 4555773dd37SAnna Dabrowska * @param string $page 4565773dd37SAnna Dabrowska * @param string $user 4575773dd37SAnna Dabrowska * @return bool 4585773dd37SAnna Dabrowska */ 4595773dd37SAnna Dabrowska public function saveAcknowledgement($page, $user) 4605773dd37SAnna Dabrowska { 4615773dd37SAnna Dabrowska $sqlite = $this->getDB(); 4625773dd37SAnna Dabrowska if (!$sqlite) return false; 4635773dd37SAnna Dabrowska 4648e55e483SAnna Dabrowska $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))"; 4655773dd37SAnna Dabrowska 466fea1a86fSAndreas Gohr $sqlite->exec($sql, $page, $user); 4675773dd37SAnna Dabrowska return true; 4685773dd37SAnna Dabrowska 4695773dd37SAnna Dabrowska } 47074126d4bSAnna Dabrowska 47174126d4bSAnna Dabrowska /** 472863b6e48SAndreas Gohr * Get all pages a user needs to acknowledge and the last acknowledge date 473d6011abdSAnna Dabrowska * 474863b6e48SAndreas Gohr * @param string $user 475863b6e48SAndreas Gohr * @param array $groups 476d6011abdSAnna Dabrowska * @return array|bool 477d6011abdSAnna Dabrowska */ 478863b6e48SAndreas Gohr public function getUserAcknowledgements($user, $groups) 479d6011abdSAnna Dabrowska { 480d6011abdSAnna Dabrowska $sqlite = $this->getDB(); 481d6011abdSAnna Dabrowska if (!$sqlite) return false; 482d6011abdSAnna Dabrowska 483f09444ffSAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, MAX(C.ack) AS ack 484863b6e48SAndreas Gohr FROM assignments A 485863b6e48SAndreas Gohr JOIN pages B 486863b6e48SAndreas Gohr ON A.page = B.page 487863b6e48SAndreas Gohr LEFT JOIN acks C 488863b6e48SAndreas Gohr ON A.page = C.page AND C.user = ? 489f09444ffSAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees, ? , ?) 490863b6e48SAndreas Gohr GROUP BY A.page 491863b6e48SAndreas Gohr ORDER BY A.page 492863b6e48SAndreas Gohr "; 493863b6e48SAndreas Gohr 494fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, [$user, $user, implode('///', $groups)]); 495863b6e48SAndreas Gohr } 496863b6e48SAndreas Gohr 497863b6e48SAndreas Gohr /** 498c6d8c1d9SAndreas Gohr * Get ack status for all assigned users of a given page 499c6d8c1d9SAndreas Gohr * 500c6d8c1d9SAndreas Gohr * This can be slow! 501c6d8c1d9SAndreas Gohr * 502c6d8c1d9SAndreas Gohr * @param string $page 503c6d8c1d9SAndreas Gohr * @return array|false 504c6d8c1d9SAndreas Gohr */ 505b6817aacSAndreas Gohr public function getPageAcknowledgements($page, $max=0) 506c6d8c1d9SAndreas Gohr { 507c6d8c1d9SAndreas Gohr $users = $this->getPageAssignees($page); 508c6d8c1d9SAndreas Gohr if ($users === false) return false; 509c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 510c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 511c6d8c1d9SAndreas Gohr 512fea1a86fSAndreas Gohr $ulist = join(',', array_map([$sqlite->getPdo(), 'quote'], $users)); 513c6d8c1d9SAndreas Gohr $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack 514c6d8c1d9SAndreas Gohr FROM pages A 515c6d8c1d9SAndreas Gohr LEFT JOIN acks B 516c6d8c1d9SAndreas Gohr ON A.page = B.page 517c6d8c1d9SAndreas Gohr AND B.user IN ($ulist) 518c6d8c1d9SAndreas Gohr WHERE A.page = ? 519c6d8c1d9SAndreas Gohr GROUP BY A.page, B.user 520c6d8c1d9SAndreas Gohr "; 521b6817aacSAndreas Gohr if($max) $sql .= " LIMIT $max"; 522fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $page); 523c6d8c1d9SAndreas Gohr 524c6d8c1d9SAndreas Gohr // there should be at least one result, unless the page is unknown 525c6d8c1d9SAndreas Gohr if (!count($acknowledgements)) return false; 526c6d8c1d9SAndreas Gohr 527c6d8c1d9SAndreas Gohr $baseinfo = [ 528c6d8c1d9SAndreas Gohr 'page' => $acknowledgements[0]['page'], 529c6d8c1d9SAndreas Gohr 'lastmod' => $acknowledgements[0]['lastmod'], 530c6d8c1d9SAndreas Gohr 'user' => null, 531c6d8c1d9SAndreas Gohr 'ack' => null, 532c6d8c1d9SAndreas Gohr ]; 533c6d8c1d9SAndreas Gohr 534c6d8c1d9SAndreas Gohr // fill up the result with all users that never acknowledged the page 535c6d8c1d9SAndreas Gohr $combined = []; 536c6d8c1d9SAndreas Gohr foreach ($acknowledgements as $ack) { 537c6d8c1d9SAndreas Gohr if ($ack['user'] !== null) { 538c6d8c1d9SAndreas Gohr $combined[$ack['user']] = $ack; 539c6d8c1d9SAndreas Gohr } 540c6d8c1d9SAndreas Gohr } 541c6d8c1d9SAndreas Gohr foreach ($users as $user) { 542c6d8c1d9SAndreas Gohr if (!isset($combined[$user])) { 543c6d8c1d9SAndreas Gohr $combined[$user] = array_merge($baseinfo, ['user' => $user]); 544c6d8c1d9SAndreas Gohr } 545c6d8c1d9SAndreas Gohr } 546c6d8c1d9SAndreas Gohr 547c6d8c1d9SAndreas Gohr ksort($combined); 548c6d8c1d9SAndreas Gohr return array_values($combined); 549c6d8c1d9SAndreas Gohr } 550c6d8c1d9SAndreas Gohr 551c6d8c1d9SAndreas Gohr /** 552863b6e48SAndreas Gohr * Returns all acknowledgements 553863b6e48SAndreas Gohr * 554863b6e48SAndreas Gohr * @param int $limit maximum number of results 555863b6e48SAndreas Gohr * @return array|bool 556863b6e48SAndreas Gohr */ 557863b6e48SAndreas Gohr public function getAcknowledgements($limit = 100) 558863b6e48SAndreas Gohr { 559863b6e48SAndreas Gohr $sqlite = $this->getDB(); 560863b6e48SAndreas Gohr if (!$sqlite) return false; 561863b6e48SAndreas Gohr 562863b6e48SAndreas Gohr $sql = ' 56384db77b6SAndreas Gohr SELECT A.page, A.user, B.lastmod, max(A.ack) AS ack 56484db77b6SAndreas Gohr FROM acks A, pages B 56584db77b6SAndreas Gohr WHERE A.page = B.page 56684db77b6SAndreas Gohr GROUP BY A.user, A.page 567863b6e48SAndreas Gohr ORDER BY ack DESC 568863b6e48SAndreas Gohr LIMIT ? 569863b6e48SAndreas Gohr '; 570fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $limit); 571d6011abdSAnna Dabrowska 572d6011abdSAnna Dabrowska return $acknowledgements; 573d6011abdSAnna Dabrowska } 574f09444ffSAndreas Gohr 575639d4c50SAndreas Gohr // endregion 5764d6d17d0SAndreas Gohr} 5774d6d17d0SAndreas Gohr 578