14d6d17d0SAndreas Gohr<?php 2c6d8c1d9SAndreas Gohr 3*3b76424dSanndause dokuwiki\Extension\Plugin; 4*3b76424dSanndause dokuwiki\ChangeLog\PageChangeLog; 5fea1a86fSAndreas Gohruse dokuwiki\ErrorHandler; 6c6d8c1d9SAndreas Gohruse dokuwiki\Extension\AuthPlugin; 7fea1a86fSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 8c6d8c1d9SAndreas Gohr 94d6d17d0SAndreas Gohr/** 104d6d17d0SAndreas Gohr * DokuWiki Plugin acknowledge (Helper Component) 114d6d17d0SAndreas Gohr * 124d6d17d0SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 134d6d17d0SAndreas Gohr * @author Andreas Gohr, Anna Dabrowska <dokuwiki@cosmocode.de> 144d6d17d0SAndreas Gohr */ 15*3b76424dSanndaclass helper_plugin_acknowledge extends Plugin 164d6d17d0SAndreas Gohr{ 17fea1a86fSAndreas Gohr protected $db; 18fea1a86fSAndreas Gohr 19639d4c50SAndreas Gohr // region Database Management 20639d4c50SAndreas Gohr 21cabb51d3SAndreas Gohr /** 22fea1a86fSAndreas Gohr * Get SQLiteDB instance 23fea1a86fSAndreas Gohr * 24fea1a86fSAndreas Gohr * @return SQLiteDB|null 25cabb51d3SAndreas Gohr */ 26cabb51d3SAndreas Gohr public function getDB() 27cabb51d3SAndreas Gohr { 28fea1a86fSAndreas Gohr if ($this->db === null) { 29fea1a86fSAndreas Gohr try { 30fea1a86fSAndreas Gohr $this->db = new SQLiteDB('acknowledgement', __DIR__ . '/db'); 31fea1a86fSAndreas Gohr 32fea1a86fSAndreas Gohr // register our custom functions 33fea1a86fSAndreas Gohr $this->db->getPdo()->sqliteCreateFunction('AUTH_ISMEMBER', [$this, 'auth_isMember'], -1); 34fea1a86fSAndreas Gohr $this->db->getPdo()->sqliteCreateFunction('MATCHES_PAGE_PATTERN', [$this, 'matchPagePattern'], 2); 35fea1a86fSAndreas Gohr } catch (\Exception $exception) { 36fea1a86fSAndreas Gohr if (defined('DOKU_UNITTEST')) throw new \RuntimeException('Could not load SQLite', 0, $exception); 37fea1a86fSAndreas Gohr ErrorHandler::logException($exception); 38cabb51d3SAndreas Gohr msg($this->getLang('error sqlite plugin missing'), -1); 39cabb51d3SAndreas Gohr return null; 40cabb51d3SAndreas Gohr } 41cabb51d3SAndreas Gohr } 42fea1a86fSAndreas Gohr return $this->db; 439c3eae1eSAnna Dabrowska } 449c3eae1eSAnna Dabrowska 459c3eae1eSAnna Dabrowska /** 469c3eae1eSAnna Dabrowska * Wrapper function for auth_isMember which accepts groups as string 479c3eae1eSAnna Dabrowska * 489c3eae1eSAnna Dabrowska * @param string $memberList 499c3eae1eSAnna Dabrowska * @param string $user 509c3eae1eSAnna Dabrowska * @param string $groups 519c3eae1eSAnna Dabrowska * @return bool 529c3eae1eSAnna Dabrowska */ 539c3eae1eSAnna Dabrowska public function auth_isMember($memberList, $user, $groups) 549c3eae1eSAnna Dabrowska { 5595113ed8SAnna Dabrowska return auth_isMember($memberList, $user, explode('///', $groups)); 569c3eae1eSAnna Dabrowska } 579c3eae1eSAnna Dabrowska 589c3eae1eSAnna Dabrowska /** 59639d4c50SAndreas Gohr * Fills the page index with all unknown pages from the fulltext index 60639d4c50SAndreas Gohr * @return void 61639d4c50SAndreas Gohr */ 62639d4c50SAndreas Gohr public function updatePageIndex() 63639d4c50SAndreas Gohr { 64639d4c50SAndreas Gohr $sqlite = $this->getDB(); 65639d4c50SAndreas Gohr if (!$sqlite) return; 66639d4c50SAndreas Gohr 67639d4c50SAndreas Gohr $pages = idx_getIndex('page', ''); 68639d4c50SAndreas Gohr $sql = "INSERT OR IGNORE INTO pages (page, lastmod) VALUES (?,?)"; 69639d4c50SAndreas Gohr 70fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 71639d4c50SAndreas Gohr foreach ($pages as $page) { 72639d4c50SAndreas Gohr $page = trim($page); 73639d4c50SAndreas Gohr $lastmod = @filemtime(wikiFN($page)); 74639d4c50SAndreas Gohr if ($lastmod) { 75fea1a86fSAndreas Gohr try { 76fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 77fea1a86fSAndreas Gohr } catch (\Exception $exception) { 78fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 79fea1a86fSAndreas Gohr throw $exception; 80639d4c50SAndreas Gohr } 81639d4c50SAndreas Gohr } 82fea1a86fSAndreas Gohr } 83fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 84639d4c50SAndreas Gohr } 85639d4c50SAndreas Gohr 86639d4c50SAndreas Gohr /** 87639d4c50SAndreas Gohr * Check if the given pattern matches the given page 88639d4c50SAndreas Gohr * 89639d4c50SAndreas Gohr * @param string $pattern the pattern to check against 90639d4c50SAndreas Gohr * @param string $page the cleaned pageid to check 91639d4c50SAndreas Gohr * @return bool 92639d4c50SAndreas Gohr */ 93639d4c50SAndreas Gohr public function matchPagePattern($pattern, $page) 94639d4c50SAndreas Gohr { 95639d4c50SAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 96639d4c50SAndreas Gohr 97639d4c50SAndreas Gohr // regex patterns 98639d4c50SAndreas Gohr if ($pattern[0] == '/') { 99639d4c50SAndreas Gohr return (bool)preg_match($pattern, ":$page"); 100639d4c50SAndreas Gohr } 101639d4c50SAndreas Gohr 102639d4c50SAndreas Gohr $pns = ':' . getNS($page) . ':'; 103639d4c50SAndreas Gohr 104639d4c50SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 105639d4c50SAndreas Gohr if (substr($pattern, -2) == '**') { 106639d4c50SAndreas Gohr // upper namespaces match 107639d4c50SAndreas Gohr if (strpos($pns, $ans) === 0) { 108639d4c50SAndreas Gohr return true; 109639d4c50SAndreas Gohr } 110639d4c50SAndreas Gohr } elseif (substr($pattern, -1) == '*') { 111639d4c50SAndreas Gohr // namespaces match exact 112*3b76424dSannda if ($ans === $pns) { 113639d4c50SAndreas Gohr return true; 114639d4c50SAndreas Gohr } 115*3b76424dSannda } elseif (cleanID($pattern) == $page) { 116639d4c50SAndreas Gohr // exact match 117639d4c50SAndreas Gohr return true; 118639d4c50SAndreas Gohr } 119639d4c50SAndreas Gohr 120639d4c50SAndreas Gohr return false; 121639d4c50SAndreas Gohr } 122639d4c50SAndreas Gohr 123639d4c50SAndreas Gohr // endregion 124639d4c50SAndreas Gohr // region Page Data 125639d4c50SAndreas Gohr 126639d4c50SAndreas Gohr /** 127ef3ab392SAndreas Gohr * Delete a page 128ef3ab392SAndreas Gohr * 129ef3ab392SAndreas Gohr * Cascades to delete all assigned data, etc. 130ef3ab392SAndreas Gohr * 131ef3ab392SAndreas Gohr * @param string $page Page ID 132ef3ab392SAndreas Gohr */ 133ef3ab392SAndreas Gohr public function removePage($page) 134ef3ab392SAndreas Gohr { 135ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 136ef3ab392SAndreas Gohr if (!$sqlite) return; 137ef3ab392SAndreas Gohr 138ef3ab392SAndreas Gohr $sql = "DELETE FROM pages WHERE page = ?"; 139fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 140ef3ab392SAndreas Gohr } 141ef3ab392SAndreas Gohr 142ef3ab392SAndreas Gohr /** 1435dee13f7SAnna Dabrowska * Update last modified date of page if content has changed 144ef3ab392SAndreas Gohr * 145ef3ab392SAndreas Gohr * @param string $page Page ID 146ef3ab392SAndreas Gohr * @param int $lastmod timestamp of last non-minor change 147ef3ab392SAndreas Gohr */ 1485dee13f7SAnna Dabrowska public function storePageDate($page, $lastmod, $newContent) 149ef3ab392SAndreas Gohr { 150*3b76424dSannda $changelog = new PageChangeLog($page); 151789aa26fSAnna Dabrowska $revs = $changelog->getRevisions(0, 1); 152ed4e8871SAnna Dabrowska 153ed4e8871SAnna Dabrowska // compare content 154ed4e8871SAnna Dabrowska $oldContent = str_replace(NL, '', io_readFile(wikiFN($page, $revs[0]))); 155ed4e8871SAnna Dabrowska $newContent = str_replace(NL, '', $newContent); 156ed4e8871SAnna Dabrowska if ($oldContent === $newContent) return; 157ed4e8871SAnna Dabrowska 158ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 159ef3ab392SAndreas Gohr if (!$sqlite) return; 160ef3ab392SAndreas Gohr 161ef3ab392SAndreas Gohr $sql = "REPLACE INTO pages (page, lastmod) VALUES (?,?)"; 162fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 163ef3ab392SAndreas Gohr } 164ef3ab392SAndreas Gohr 165639d4c50SAndreas Gohr // endregion 166639d4c50SAndreas Gohr // region Assignments 167639d4c50SAndreas Gohr 168ef3ab392SAndreas Gohr /** 169f09444ffSAndreas Gohr * Clears direct assignments for a page 170f09444ffSAndreas Gohr * 171cabb51d3SAndreas Gohr * @param string $page Page ID 172cabb51d3SAndreas Gohr */ 173f09444ffSAndreas Gohr public function clearPageAssignments($page) 174cabb51d3SAndreas Gohr { 175cabb51d3SAndreas Gohr $sqlite = $this->getDB(); 176cabb51d3SAndreas Gohr if (!$sqlite) return; 177cabb51d3SAndreas Gohr 178f09444ffSAndreas Gohr $sql = "UPDATE assignments SET pageassignees = '' WHERE page = ?"; 179fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 180f09444ffSAndreas Gohr } 181f09444ffSAndreas Gohr 182f09444ffSAndreas Gohr /** 183639d4c50SAndreas Gohr * Set assignees for a given page as manually specified 184639d4c50SAndreas Gohr * 185639d4c50SAndreas Gohr * @param string $page Page ID 186639d4c50SAndreas Gohr * @param string $assignees 187639d4c50SAndreas Gohr * @return void 188639d4c50SAndreas Gohr */ 189639d4c50SAndreas Gohr public function setPageAssignees($page, $assignees) 190639d4c50SAndreas Gohr { 191639d4c50SAndreas Gohr $sqlite = $this->getDB(); 192639d4c50SAndreas Gohr if (!$sqlite) return; 193639d4c50SAndreas Gohr 194*3b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 195639d4c50SAndreas Gohr 196639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'pageassignees') VALUES (?,?)"; 197fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 198639d4c50SAndreas Gohr } 199639d4c50SAndreas Gohr 200639d4c50SAndreas Gohr /** 201639d4c50SAndreas Gohr * Set assignees for a given page from the patterns 202639d4c50SAndreas Gohr * @param string $page Page ID 203639d4c50SAndreas Gohr */ 204639d4c50SAndreas Gohr public function setAutoAssignees($page) 205639d4c50SAndreas Gohr { 206639d4c50SAndreas Gohr $sqlite = $this->getDB(); 207639d4c50SAndreas Gohr if (!$sqlite) return; 208639d4c50SAndreas Gohr 209639d4c50SAndreas Gohr $patterns = $this->getAssignmentPatterns(); 210639d4c50SAndreas Gohr 211639d4c50SAndreas Gohr // given assignees 212639d4c50SAndreas Gohr $assignees = ''; 213639d4c50SAndreas Gohr 214639d4c50SAndreas Gohr // find all patterns that match the page and add the configured assignees 215639d4c50SAndreas Gohr foreach ($patterns as $pattern => $assignees) { 216639d4c50SAndreas Gohr if ($this->matchPagePattern($pattern, $page)) { 217639d4c50SAndreas Gohr $assignees .= ',' . $assignees; 218639d4c50SAndreas Gohr } 219639d4c50SAndreas Gohr } 220639d4c50SAndreas Gohr 221639d4c50SAndreas Gohr // remove duplicates and empty entries 222*3b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 223639d4c50SAndreas Gohr 224639d4c50SAndreas Gohr // store the assignees 225639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'autoassignees') VALUES (?,?)"; 226fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 227639d4c50SAndreas Gohr } 228639d4c50SAndreas Gohr 229639d4c50SAndreas Gohr /** 230639d4c50SAndreas Gohr * Is the given user one of the assignees for this page 231639d4c50SAndreas Gohr * 232639d4c50SAndreas Gohr * @param string $page Page ID 233639d4c50SAndreas Gohr * @param string $user user name to check 234639d4c50SAndreas Gohr * @param string[] $groups groups this user is in 235639d4c50SAndreas Gohr * @return bool 236639d4c50SAndreas Gohr */ 237639d4c50SAndreas Gohr public function isUserAssigned($page, $user, $groups) 238639d4c50SAndreas Gohr { 239639d4c50SAndreas Gohr $sqlite = $this->getDB(); 240639d4c50SAndreas Gohr if (!$sqlite) return false; 241639d4c50SAndreas Gohr 242639d4c50SAndreas Gohr $sql = "SELECT pageassignees,autoassignees FROM assignments WHERE page = ?"; 243fea1a86fSAndreas Gohr $record = $sqlite->queryRecord($sql, $page); 244a806aa3dSSven if (!$record) return false; 245fea1a86fSAndreas Gohr $assignees = $record['pageassignees'] . ',' . $record['autoassignees']; 246639d4c50SAndreas Gohr return auth_isMember($assignees, $user, $groups); 247639d4c50SAndreas Gohr } 248639d4c50SAndreas Gohr 249639d4c50SAndreas Gohr /** 250639d4c50SAndreas Gohr * Fetch all assignments for a given user, with additional page information, 251833123deSAnna Dabrowska * by default filtering already granted acknowledgements. 252833123deSAnna Dabrowska * Filter can be switched off via $includeDone 253639d4c50SAndreas Gohr * 254639d4c50SAndreas Gohr * @param string $user 255639d4c50SAndreas Gohr * @param array $groups 256833123deSAnna Dabrowska * @param bool $includeDone 257833123deSAnna Dabrowska * 258639d4c50SAndreas Gohr * @return array|bool 259639d4c50SAndreas Gohr */ 260833123deSAnna Dabrowska public function getUserAssignments($user, $groups, $includeDone = false) 261639d4c50SAndreas Gohr { 262639d4c50SAndreas Gohr $sqlite = $this->getDB(); 263639d4c50SAndreas Gohr if (!$sqlite) return false; 264639d4c50SAndreas Gohr 265639d4c50SAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, C.ack FROM assignments A 266639d4c50SAndreas Gohr JOIN pages B 267639d4c50SAndreas Gohr ON A.page = B.page 268639d4c50SAndreas Gohr LEFT JOIN acks C 269639d4c50SAndreas Gohr ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) ) 270833123deSAnna Dabrowska WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees , ? , ?)"; 271833123deSAnna Dabrowska 272833123deSAnna Dabrowska if (!$includeDone) { 273833123deSAnna Dabrowska $sql .= ' AND ack IS NULL'; 274833123deSAnna Dabrowska } 275639d4c50SAndreas Gohr 276fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, $user, $user, implode('///', $groups)); 277639d4c50SAndreas Gohr } 278639d4c50SAndreas Gohr 279639d4c50SAndreas Gohr 280639d4c50SAndreas Gohr /** 281639d4c50SAndreas Gohr * Resolve names of users assigned to a given page 282639d4c50SAndreas Gohr * 283639d4c50SAndreas Gohr * This can be slow on huge user bases! 284639d4c50SAndreas Gohr * 285639d4c50SAndreas Gohr * @param string $page 286639d4c50SAndreas Gohr * @return array|false 287639d4c50SAndreas Gohr */ 288639d4c50SAndreas Gohr public function getPageAssignees($page) 289639d4c50SAndreas Gohr { 290639d4c50SAndreas Gohr $sqlite = $this->getDB(); 291639d4c50SAndreas Gohr if (!$sqlite) return false; 292639d4c50SAndreas Gohr /** @var AuthPlugin $auth */ 293639d4c50SAndreas Gohr global $auth; 294639d4c50SAndreas Gohr 295639d4c50SAndreas Gohr $sql = "SELECT pageassignees || ',' || autoassignees AS 'assignments' 296639d4c50SAndreas Gohr FROM assignments 297639d4c50SAndreas Gohr WHERE page = ?"; 298fea1a86fSAndreas Gohr $assignments = $sqlite->queryValue($sql, $page); 299639d4c50SAndreas Gohr 300639d4c50SAndreas Gohr $users = []; 301639d4c50SAndreas Gohr foreach (explode(',', $assignments) as $item) { 302639d4c50SAndreas Gohr $item = trim($item); 303639d4c50SAndreas Gohr if ($item === '') continue; 304639d4c50SAndreas Gohr if ($item[0] == '@') { 305639d4c50SAndreas Gohr $users = array_merge( 306639d4c50SAndreas Gohr $users, 307639d4c50SAndreas Gohr array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)])) 308639d4c50SAndreas Gohr ); 309639d4c50SAndreas Gohr } else { 310639d4c50SAndreas Gohr $users[] = $item; 311639d4c50SAndreas Gohr } 312639d4c50SAndreas Gohr } 313639d4c50SAndreas Gohr 314639d4c50SAndreas Gohr return array_unique($users); 315639d4c50SAndreas Gohr } 316639d4c50SAndreas Gohr 317639d4c50SAndreas Gohr // endregion 318639d4c50SAndreas Gohr // region Assignment Patterns 319639d4c50SAndreas Gohr 320639d4c50SAndreas Gohr /** 321f09444ffSAndreas Gohr * Get all the assignment patterns 322f09444ffSAndreas Gohr * @return array (pattern => assignees) 323f09444ffSAndreas Gohr */ 324f09444ffSAndreas Gohr public function getAssignmentPatterns() 325f09444ffSAndreas Gohr { 326f09444ffSAndreas Gohr $sqlite = $this->getDB(); 327f09444ffSAndreas Gohr if (!$sqlite) return []; 328f09444ffSAndreas Gohr 329f09444ffSAndreas Gohr $sql = "SELECT pattern, assignees FROM assignments_patterns"; 330fea1a86fSAndreas Gohr return $sqlite->queryKeyValueList($sql); 331f09444ffSAndreas Gohr } 332f09444ffSAndreas Gohr 333f09444ffSAndreas Gohr /** 334f09444ffSAndreas Gohr * Save new assignment patterns 335f09444ffSAndreas Gohr * 336f09444ffSAndreas Gohr * This resaves all patterns and reapplies them 337f09444ffSAndreas Gohr * 338f09444ffSAndreas Gohr * @param array $patterns (pattern => assignees) 339f09444ffSAndreas Gohr */ 340639d4c50SAndreas Gohr public function saveAssignmentPatterns($patterns) 341639d4c50SAndreas Gohr { 342f09444ffSAndreas Gohr $sqlite = $this->getDB(); 343f09444ffSAndreas Gohr if (!$sqlite) return; 344f09444ffSAndreas Gohr 345fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 346fea1a86fSAndreas Gohr try { 347f09444ffSAndreas Gohr 348fea1a86fSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all assignments */ 349f09444ffSAndreas Gohr $sql = "UPDATE assignments SET autoassignees = ''"; 350fea1a86fSAndreas Gohr $sqlite->exec($sql); 351f09444ffSAndreas Gohr 352f09444ffSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all patterns */ 353f09444ffSAndreas Gohr $sql = "DELETE FROM assignments_patterns"; 354fea1a86fSAndreas Gohr $sqlite->exec($sql); 355f09444ffSAndreas Gohr 356f09444ffSAndreas Gohr // insert new patterns and gather affected pages 357f09444ffSAndreas Gohr $pages = []; 358f09444ffSAndreas Gohr 359f09444ffSAndreas Gohr $sql = "REPLACE INTO assignments_patterns (pattern, assignees) VALUES (?,?)"; 360f09444ffSAndreas Gohr foreach ($patterns as $pattern => $assignees) { 361f09444ffSAndreas Gohr $pattern = trim($pattern); 362f09444ffSAndreas Gohr $assignees = trim($assignees); 363f09444ffSAndreas Gohr if (!$pattern || !$assignees) continue; 364fea1a86fSAndreas Gohr $sqlite->exec($sql, [$pattern, $assignees]); 365f09444ffSAndreas Gohr 366f09444ffSAndreas Gohr // patterns may overlap, so we need to gather all affected pages first 367f09444ffSAndreas Gohr $affectedPages = $this->getPagesMatchingPattern($pattern); 368f09444ffSAndreas Gohr foreach ($affectedPages as $page) { 369f09444ffSAndreas Gohr if (isset($pages[$page])) { 370f09444ffSAndreas Gohr $pages[$page] .= ',' . $assignees; 371f09444ffSAndreas Gohr } else { 372f09444ffSAndreas Gohr $pages[$page] = $assignees; 373f09444ffSAndreas Gohr } 374f09444ffSAndreas Gohr } 375f09444ffSAndreas Gohr } 376f09444ffSAndreas Gohr 377f09444ffSAndreas Gohr $sql = "INSERT INTO assignments (page, autoassignees) VALUES (?, ?) 378f09444ffSAndreas Gohr ON CONFLICT(page) 379f09444ffSAndreas Gohr DO UPDATE SET autoassignees = ?"; 380f09444ffSAndreas Gohr foreach ($pages as $page => $assignees) { 381f09444ffSAndreas Gohr // remove duplicates and empty entries 382*3b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 383fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees, $assignees]); 384f09444ffSAndreas Gohr } 385fea1a86fSAndreas Gohr } catch (Exception $e) { 386fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 387fea1a86fSAndreas Gohr throw $e; 388fea1a86fSAndreas Gohr } 389fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 390f09444ffSAndreas Gohr } 391f09444ffSAndreas Gohr 392f09444ffSAndreas Gohr /** 393f09444ffSAndreas Gohr * Get all known pages that match the given pattern 394f09444ffSAndreas Gohr * 395f09444ffSAndreas Gohr * @param $pattern 396f09444ffSAndreas Gohr * @return string[] 397f09444ffSAndreas Gohr */ 398639d4c50SAndreas Gohr public function getPagesMatchingPattern($pattern) 399639d4c50SAndreas Gohr { 400f09444ffSAndreas Gohr $sqlite = $this->getDB(); 401f09444ffSAndreas Gohr if (!$sqlite) return []; 402f09444ffSAndreas Gohr 403f09444ffSAndreas Gohr $sql = "SELECT page FROM pages WHERE MATCHES_PAGE_PATTERN(?, page)"; 404fea1a86fSAndreas Gohr $pages = $sqlite->queryAll($sql, $pattern); 405f09444ffSAndreas Gohr 406f09444ffSAndreas Gohr return array_column($pages, 'page'); 407f09444ffSAndreas Gohr } 408f09444ffSAndreas Gohr 409639d4c50SAndreas Gohr // endregion 410639d4c50SAndreas Gohr // region Acknowledgements 411ef3ab392SAndreas Gohr 412ef3ab392SAndreas Gohr /** 413ef3ab392SAndreas Gohr * Has the given user acknowledged the given page? 414ef3ab392SAndreas Gohr * 415ef3ab392SAndreas Gohr * @param string $page 416ef3ab392SAndreas Gohr * @param string $user 4175773dd37SAnna Dabrowska * @return bool|int timestamp of acknowledgement or false 418ef3ab392SAndreas Gohr */ 419ef3ab392SAndreas Gohr public function hasUserAcknowledged($page, $user) 420ef3ab392SAndreas Gohr { 421ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 422ef3ab392SAndreas Gohr if (!$sqlite) return false; 423ef3ab392SAndreas Gohr 424ef3ab392SAndreas Gohr $sql = "SELECT ack 425ef3ab392SAndreas Gohr FROM acks A, pages B 426ef3ab392SAndreas Gohr WHERE A.page = B.page 4275773dd37SAnna Dabrowska AND A.page = ? 4285773dd37SAnna Dabrowska AND A.user = ? 429ef3ab392SAndreas Gohr AND A.ack >= B.lastmod"; 430ef3ab392SAndreas Gohr 431fea1a86fSAndreas Gohr $acktime = $sqlite->queryValue($sql, $page, $user); 432ef3ab392SAndreas Gohr 433ef3ab392SAndreas Gohr return $acktime ? (int)$acktime : false; 434ef3ab392SAndreas Gohr } 4355773dd37SAnna Dabrowska 4365773dd37SAnna Dabrowska /** 437d9a8334dSAnna Dabrowska * Timestamp of the latest acknowledgment of the given page 438d9a8334dSAnna Dabrowska * by the given user 439d9a8334dSAnna Dabrowska * 440d9a8334dSAnna Dabrowska * @param string $page 441d9a8334dSAnna Dabrowska * @param string $user 442d9a8334dSAnna Dabrowska * @return bool|string 443d9a8334dSAnna Dabrowska */ 444d9a8334dSAnna Dabrowska public function getLatestUserAcknowledgement($page, $user) 445d9a8334dSAnna Dabrowska { 446d9a8334dSAnna Dabrowska $sqlite = $this->getDB(); 447d9a8334dSAnna Dabrowska if (!$sqlite) return false; 448d9a8334dSAnna Dabrowska 449d9a8334dSAnna Dabrowska $sql = "SELECT MAX(ack) 450d9a8334dSAnna Dabrowska FROM acks 451d9a8334dSAnna Dabrowska WHERE page = ? 452d9a8334dSAnna Dabrowska AND user = ?"; 453d9a8334dSAnna Dabrowska 454fea1a86fSAndreas Gohr return $sqlite->queryValue($sql, [$page, $user]); 455d9a8334dSAnna Dabrowska } 456d9a8334dSAnna Dabrowska 457d9a8334dSAnna Dabrowska /** 4585773dd37SAnna Dabrowska * Save user's acknowledgement for a given page 4595773dd37SAnna Dabrowska * 4605773dd37SAnna Dabrowska * @param string $page 4615773dd37SAnna Dabrowska * @param string $user 4625773dd37SAnna Dabrowska * @return bool 4635773dd37SAnna Dabrowska */ 4645773dd37SAnna Dabrowska public function saveAcknowledgement($page, $user) 4655773dd37SAnna Dabrowska { 4665773dd37SAnna Dabrowska $sqlite = $this->getDB(); 4675773dd37SAnna Dabrowska if (!$sqlite) return false; 4685773dd37SAnna Dabrowska 4698e55e483SAnna Dabrowska $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))"; 4705773dd37SAnna Dabrowska 471fea1a86fSAndreas Gohr $sqlite->exec($sql, $page, $user); 4725773dd37SAnna Dabrowska return true; 4735773dd37SAnna Dabrowska } 47474126d4bSAnna Dabrowska 47574126d4bSAnna Dabrowska /** 476863b6e48SAndreas Gohr * Get all pages a user needs to acknowledge and the last acknowledge date 477d6011abdSAnna Dabrowska * 478863b6e48SAndreas Gohr * @param string $user 479863b6e48SAndreas Gohr * @param array $groups 480d6011abdSAnna Dabrowska * @return array|bool 481d6011abdSAnna Dabrowska */ 482863b6e48SAndreas Gohr public function getUserAcknowledgements($user, $groups) 483d6011abdSAnna Dabrowska { 484d6011abdSAnna Dabrowska $sqlite = $this->getDB(); 485d6011abdSAnna Dabrowska if (!$sqlite) return false; 486d6011abdSAnna Dabrowska 487f09444ffSAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, MAX(C.ack) AS ack 488863b6e48SAndreas Gohr FROM assignments A 489863b6e48SAndreas Gohr JOIN pages B 490863b6e48SAndreas Gohr ON A.page = B.page 491863b6e48SAndreas Gohr LEFT JOIN acks C 492863b6e48SAndreas Gohr ON A.page = C.page AND C.user = ? 493f09444ffSAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees, ? , ?) 494863b6e48SAndreas Gohr GROUP BY A.page 495863b6e48SAndreas Gohr ORDER BY A.page 496863b6e48SAndreas Gohr "; 497863b6e48SAndreas Gohr 498fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, [$user, $user, implode('///', $groups)]); 499863b6e48SAndreas Gohr } 500863b6e48SAndreas Gohr 501863b6e48SAndreas Gohr /** 502c6d8c1d9SAndreas Gohr * Get ack status for all assigned users of a given page 503c6d8c1d9SAndreas Gohr * 504c6d8c1d9SAndreas Gohr * This can be slow! 505c6d8c1d9SAndreas Gohr * 506c6d8c1d9SAndreas Gohr * @param string $page 507c6d8c1d9SAndreas Gohr * @return array|false 508c6d8c1d9SAndreas Gohr */ 509b6817aacSAndreas Gohr public function getPageAcknowledgements($page, $max = 0) 510c6d8c1d9SAndreas Gohr { 511c6d8c1d9SAndreas Gohr $users = $this->getPageAssignees($page); 512c6d8c1d9SAndreas Gohr if ($users === false) return false; 513c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 514c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 515c6d8c1d9SAndreas Gohr 516*3b76424dSannda $ulist = implode(',', array_map([$sqlite->getPdo(), 'quote'], $users)); 517c6d8c1d9SAndreas Gohr $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack 518c6d8c1d9SAndreas Gohr FROM pages A 519c6d8c1d9SAndreas Gohr LEFT JOIN acks B 520c6d8c1d9SAndreas Gohr ON A.page = B.page 521c6d8c1d9SAndreas Gohr AND B.user IN ($ulist) 522c6d8c1d9SAndreas Gohr WHERE A.page = ? 523c6d8c1d9SAndreas Gohr GROUP BY A.page, B.user 524c6d8c1d9SAndreas Gohr "; 525b6817aacSAndreas Gohr if ($max) $sql .= " LIMIT $max"; 526fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $page); 527c6d8c1d9SAndreas Gohr 528c6d8c1d9SAndreas Gohr // there should be at least one result, unless the page is unknown 529c6d8c1d9SAndreas Gohr if (!count($acknowledgements)) return false; 530c6d8c1d9SAndreas Gohr 531c6d8c1d9SAndreas Gohr $baseinfo = [ 532c6d8c1d9SAndreas Gohr 'page' => $acknowledgements[0]['page'], 533c6d8c1d9SAndreas Gohr 'lastmod' => $acknowledgements[0]['lastmod'], 534c6d8c1d9SAndreas Gohr 'user' => null, 535c6d8c1d9SAndreas Gohr 'ack' => null, 536c6d8c1d9SAndreas Gohr ]; 537c6d8c1d9SAndreas Gohr 538c6d8c1d9SAndreas Gohr // fill up the result with all users that never acknowledged the page 539c6d8c1d9SAndreas Gohr $combined = []; 540c6d8c1d9SAndreas Gohr foreach ($acknowledgements as $ack) { 541c6d8c1d9SAndreas Gohr if ($ack['user'] !== null) { 542c6d8c1d9SAndreas Gohr $combined[$ack['user']] = $ack; 543c6d8c1d9SAndreas Gohr } 544c6d8c1d9SAndreas Gohr } 545c6d8c1d9SAndreas Gohr foreach ($users as $user) { 546c6d8c1d9SAndreas Gohr if (!isset($combined[$user])) { 547c6d8c1d9SAndreas Gohr $combined[$user] = array_merge($baseinfo, ['user' => $user]); 548c6d8c1d9SAndreas Gohr } 549c6d8c1d9SAndreas Gohr } 550c6d8c1d9SAndreas Gohr 551c6d8c1d9SAndreas Gohr ksort($combined); 552c6d8c1d9SAndreas Gohr return array_values($combined); 553c6d8c1d9SAndreas Gohr } 554c6d8c1d9SAndreas Gohr 555c6d8c1d9SAndreas Gohr /** 556863b6e48SAndreas Gohr * Returns all acknowledgements 557863b6e48SAndreas Gohr * 558863b6e48SAndreas Gohr * @param int $limit maximum number of results 559863b6e48SAndreas Gohr * @return array|bool 560863b6e48SAndreas Gohr */ 561863b6e48SAndreas Gohr public function getAcknowledgements($limit = 100) 562863b6e48SAndreas Gohr { 563863b6e48SAndreas Gohr $sqlite = $this->getDB(); 564863b6e48SAndreas Gohr if (!$sqlite) return false; 565863b6e48SAndreas Gohr 566863b6e48SAndreas Gohr $sql = ' 56784db77b6SAndreas Gohr SELECT A.page, A.user, B.lastmod, max(A.ack) AS ack 56884db77b6SAndreas Gohr FROM acks A, pages B 56984db77b6SAndreas Gohr WHERE A.page = B.page 57084db77b6SAndreas Gohr GROUP BY A.user, A.page 571863b6e48SAndreas Gohr ORDER BY ack DESC 572863b6e48SAndreas Gohr LIMIT ? 573863b6e48SAndreas Gohr '; 574fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $limit); 575d6011abdSAnna Dabrowska 576d6011abdSAnna Dabrowska return $acknowledgements; 577d6011abdSAnna Dabrowska } 578f09444ffSAndreas Gohr 579639d4c50SAndreas Gohr // endregion 5804d6d17d0SAndreas Gohr} 581