14d6d17d0SAndreas Gohr<?php 2c6d8c1d9SAndreas Gohr 33b76424dSanndause dokuwiki\Extension\Plugin; 43b76424dSanndause 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 */ 153b76424dSanndaclass 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 51ba917e33SAnna Dabrowska * 529c3eae1eSAnna Dabrowska * @return bool 539c3eae1eSAnna Dabrowska */ 54ba917e33SAnna Dabrowska // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 559c3eae1eSAnna Dabrowska public function auth_isMember($memberList, $user, $groups) 569c3eae1eSAnna Dabrowska { 5795113ed8SAnna Dabrowska return auth_isMember($memberList, $user, explode('///', $groups)); 589c3eae1eSAnna Dabrowska } 599c3eae1eSAnna Dabrowska 609c3eae1eSAnna Dabrowska /** 61639d4c50SAndreas Gohr * Fills the page index with all unknown pages from the fulltext index 62639d4c50SAndreas Gohr * @return void 63639d4c50SAndreas Gohr */ 64639d4c50SAndreas Gohr public function updatePageIndex() 65639d4c50SAndreas Gohr { 66639d4c50SAndreas Gohr $sqlite = $this->getDB(); 67639d4c50SAndreas Gohr if (!$sqlite) return; 68639d4c50SAndreas Gohr 69639d4c50SAndreas Gohr $pages = idx_getIndex('page', ''); 70639d4c50SAndreas Gohr $sql = "INSERT OR IGNORE INTO pages (page, lastmod) VALUES (?,?)"; 71639d4c50SAndreas Gohr 72fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 73639d4c50SAndreas Gohr foreach ($pages as $page) { 74639d4c50SAndreas Gohr $page = trim($page); 75639d4c50SAndreas Gohr $lastmod = @filemtime(wikiFN($page)); 76639d4c50SAndreas Gohr if ($lastmod) { 77fea1a86fSAndreas Gohr try { 78fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 79fea1a86fSAndreas Gohr } catch (\Exception $exception) { 80fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 81fea1a86fSAndreas Gohr throw $exception; 82639d4c50SAndreas Gohr } 83639d4c50SAndreas Gohr } 84fea1a86fSAndreas Gohr } 85fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 86639d4c50SAndreas Gohr } 87639d4c50SAndreas Gohr 88639d4c50SAndreas Gohr /** 89639d4c50SAndreas Gohr * Check if the given pattern matches the given page 90639d4c50SAndreas Gohr * 91639d4c50SAndreas Gohr * @param string $pattern the pattern to check against 92639d4c50SAndreas Gohr * @param string $page the cleaned pageid to check 93639d4c50SAndreas Gohr * @return bool 94639d4c50SAndreas Gohr */ 95639d4c50SAndreas Gohr public function matchPagePattern($pattern, $page) 96639d4c50SAndreas Gohr { 97639d4c50SAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 98639d4c50SAndreas Gohr 99639d4c50SAndreas Gohr // regex patterns 100639d4c50SAndreas Gohr if ($pattern[0] == '/') { 101639d4c50SAndreas Gohr return (bool)preg_match($pattern, ":$page"); 102639d4c50SAndreas Gohr } 103639d4c50SAndreas Gohr 104639d4c50SAndreas Gohr $pns = ':' . getNS($page) . ':'; 105639d4c50SAndreas Gohr 106639d4c50SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 107639d4c50SAndreas Gohr if (substr($pattern, -2) == '**') { 108639d4c50SAndreas Gohr // upper namespaces match 109639d4c50SAndreas Gohr if (strpos($pns, $ans) === 0) { 110639d4c50SAndreas Gohr return true; 111639d4c50SAndreas Gohr } 112639d4c50SAndreas Gohr } elseif (substr($pattern, -1) == '*') { 113639d4c50SAndreas Gohr // namespaces match exact 1143b76424dSannda if ($ans === $pns) { 115639d4c50SAndreas Gohr return true; 116639d4c50SAndreas Gohr } 1173b76424dSannda } elseif (cleanID($pattern) == $page) { 118639d4c50SAndreas Gohr // exact match 119639d4c50SAndreas Gohr return true; 120639d4c50SAndreas Gohr } 121639d4c50SAndreas Gohr 122639d4c50SAndreas Gohr return false; 123639d4c50SAndreas Gohr } 124639d4c50SAndreas Gohr 125*45240794SAnna Dabrowska /** 126*45240794SAnna Dabrowska * 127*45240794SAnna Dabrowska * @return array 128*45240794SAnna Dabrowska */ 129*45240794SAnna Dabrowska public function getUsers() 130*45240794SAnna Dabrowska { 131*45240794SAnna Dabrowska /** @var AuthPlugin $auth */ 132*45240794SAnna Dabrowska global $auth; 133*45240794SAnna Dabrowska 134*45240794SAnna Dabrowska if (!$auth->canDo('getUsers')) { 135*45240794SAnna Dabrowska return []; 136*45240794SAnna Dabrowska } 137*45240794SAnna Dabrowska 138*45240794SAnna Dabrowska $cb = function($k, $v) { 139*45240794SAnna Dabrowska return [ 140*45240794SAnna Dabrowska 'value' => $k, 141*45240794SAnna Dabrowska 'label' => $k . ' (' . $v['name'] . ')' 142*45240794SAnna Dabrowska ]; 143*45240794SAnna Dabrowska }; 144*45240794SAnna Dabrowska $users = $auth->retrieveUsers(); 145*45240794SAnna Dabrowska $users = array_map($cb, array_keys($users), array_values($users)); 146*45240794SAnna Dabrowska 147*45240794SAnna Dabrowska return $users; 148*45240794SAnna Dabrowska } 149*45240794SAnna Dabrowska 150639d4c50SAndreas Gohr // endregion 151639d4c50SAndreas Gohr // region Page Data 152639d4c50SAndreas Gohr 153639d4c50SAndreas Gohr /** 154ef3ab392SAndreas Gohr * Delete a page 155ef3ab392SAndreas Gohr * 156ef3ab392SAndreas Gohr * Cascades to delete all assigned data, etc. 157ef3ab392SAndreas Gohr * 158ef3ab392SAndreas Gohr * @param string $page Page ID 159ef3ab392SAndreas Gohr */ 160ef3ab392SAndreas Gohr public function removePage($page) 161ef3ab392SAndreas Gohr { 162ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 163ef3ab392SAndreas Gohr if (!$sqlite) return; 164ef3ab392SAndreas Gohr 165ef3ab392SAndreas Gohr $sql = "DELETE FROM pages WHERE page = ?"; 166fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 167ef3ab392SAndreas Gohr } 168ef3ab392SAndreas Gohr 169ef3ab392SAndreas Gohr /** 1705dee13f7SAnna Dabrowska * Update last modified date of page if content has changed 171ef3ab392SAndreas Gohr * 172ef3ab392SAndreas Gohr * @param string $page Page ID 173ef3ab392SAndreas Gohr * @param int $lastmod timestamp of last non-minor change 174ef3ab392SAndreas Gohr */ 1755dee13f7SAnna Dabrowska public function storePageDate($page, $lastmod, $newContent) 176ef3ab392SAndreas Gohr { 1773b76424dSannda $changelog = new PageChangeLog($page); 178789aa26fSAnna Dabrowska $revs = $changelog->getRevisions(0, 1); 179ed4e8871SAnna Dabrowska 180ed4e8871SAnna Dabrowska // compare content 181ed4e8871SAnna Dabrowska $oldContent = str_replace(NL, '', io_readFile(wikiFN($page, $revs[0]))); 182ed4e8871SAnna Dabrowska $newContent = str_replace(NL, '', $newContent); 183ed4e8871SAnna Dabrowska if ($oldContent === $newContent) return; 184ed4e8871SAnna Dabrowska 185ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 186ef3ab392SAndreas Gohr if (!$sqlite) return; 187ef3ab392SAndreas Gohr 188ef3ab392SAndreas Gohr $sql = "REPLACE INTO pages (page, lastmod) VALUES (?,?)"; 189fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $lastmod]); 190ef3ab392SAndreas Gohr } 191ef3ab392SAndreas Gohr 192639d4c50SAndreas Gohr // endregion 193639d4c50SAndreas Gohr // region Assignments 194639d4c50SAndreas Gohr 195ef3ab392SAndreas Gohr /** 196f09444ffSAndreas Gohr * Clears direct assignments for a page 197f09444ffSAndreas Gohr * 198cabb51d3SAndreas Gohr * @param string $page Page ID 199cabb51d3SAndreas Gohr */ 200f09444ffSAndreas Gohr public function clearPageAssignments($page) 201cabb51d3SAndreas Gohr { 202cabb51d3SAndreas Gohr $sqlite = $this->getDB(); 203cabb51d3SAndreas Gohr if (!$sqlite) return; 204cabb51d3SAndreas Gohr 205f09444ffSAndreas Gohr $sql = "UPDATE assignments SET pageassignees = '' WHERE page = ?"; 206fea1a86fSAndreas Gohr $sqlite->exec($sql, $page); 207f09444ffSAndreas Gohr } 208f09444ffSAndreas Gohr 209f09444ffSAndreas Gohr /** 210639d4c50SAndreas Gohr * Set assignees for a given page as manually specified 211639d4c50SAndreas Gohr * 212639d4c50SAndreas Gohr * @param string $page Page ID 213639d4c50SAndreas Gohr * @param string $assignees 214639d4c50SAndreas Gohr * @return void 215639d4c50SAndreas Gohr */ 216639d4c50SAndreas Gohr public function setPageAssignees($page, $assignees) 217639d4c50SAndreas Gohr { 218639d4c50SAndreas Gohr $sqlite = $this->getDB(); 219639d4c50SAndreas Gohr if (!$sqlite) return; 220639d4c50SAndreas Gohr 2213b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 222639d4c50SAndreas Gohr 223639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'pageassignees') VALUES (?,?)"; 224fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 225639d4c50SAndreas Gohr } 226639d4c50SAndreas Gohr 227639d4c50SAndreas Gohr /** 228639d4c50SAndreas Gohr * Set assignees for a given page from the patterns 229639d4c50SAndreas Gohr * @param string $page Page ID 230639d4c50SAndreas Gohr */ 231639d4c50SAndreas Gohr public function setAutoAssignees($page) 232639d4c50SAndreas Gohr { 233639d4c50SAndreas Gohr $sqlite = $this->getDB(); 234639d4c50SAndreas Gohr if (!$sqlite) return; 235639d4c50SAndreas Gohr 236639d4c50SAndreas Gohr $patterns = $this->getAssignmentPatterns(); 237639d4c50SAndreas Gohr 238639d4c50SAndreas Gohr // given assignees 239639d4c50SAndreas Gohr $assignees = ''; 240639d4c50SAndreas Gohr 241639d4c50SAndreas Gohr // find all patterns that match the page and add the configured assignees 242639d4c50SAndreas Gohr foreach ($patterns as $pattern => $assignees) { 243639d4c50SAndreas Gohr if ($this->matchPagePattern($pattern, $page)) { 244639d4c50SAndreas Gohr $assignees .= ',' . $assignees; 245639d4c50SAndreas Gohr } 246639d4c50SAndreas Gohr } 247639d4c50SAndreas Gohr 248639d4c50SAndreas Gohr // remove duplicates and empty entries 2493b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 250639d4c50SAndreas Gohr 251639d4c50SAndreas Gohr // store the assignees 252639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'autoassignees') VALUES (?,?)"; 253fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees]); 254639d4c50SAndreas Gohr } 255639d4c50SAndreas Gohr 256639d4c50SAndreas Gohr /** 257639d4c50SAndreas Gohr * Is the given user one of the assignees for this page 258639d4c50SAndreas Gohr * 259639d4c50SAndreas Gohr * @param string $page Page ID 260639d4c50SAndreas Gohr * @param string $user user name to check 261639d4c50SAndreas Gohr * @param string[] $groups groups this user is in 262639d4c50SAndreas Gohr * @return bool 263639d4c50SAndreas Gohr */ 264639d4c50SAndreas Gohr public function isUserAssigned($page, $user, $groups) 265639d4c50SAndreas Gohr { 266639d4c50SAndreas Gohr $sqlite = $this->getDB(); 267639d4c50SAndreas Gohr if (!$sqlite) return false; 268639d4c50SAndreas Gohr 269639d4c50SAndreas Gohr $sql = "SELECT pageassignees,autoassignees FROM assignments WHERE page = ?"; 270fea1a86fSAndreas Gohr $record = $sqlite->queryRecord($sql, $page); 271a806aa3dSSven if (!$record) return false; 272fea1a86fSAndreas Gohr $assignees = $record['pageassignees'] . ',' . $record['autoassignees']; 273639d4c50SAndreas Gohr return auth_isMember($assignees, $user, $groups); 274639d4c50SAndreas Gohr } 275639d4c50SAndreas Gohr 276639d4c50SAndreas Gohr /** 277639d4c50SAndreas Gohr * Fetch all assignments for a given user, with additional page information, 278833123deSAnna Dabrowska * by default filtering already granted acknowledgements. 279833123deSAnna Dabrowska * Filter can be switched off via $includeDone 280639d4c50SAndreas Gohr * 281639d4c50SAndreas Gohr * @param string $user 282639d4c50SAndreas Gohr * @param array $groups 283833123deSAnna Dabrowska * @param bool $includeDone 284833123deSAnna Dabrowska * 285639d4c50SAndreas Gohr * @return array|bool 286639d4c50SAndreas Gohr */ 287833123deSAnna Dabrowska public function getUserAssignments($user, $groups, $includeDone = false) 288639d4c50SAndreas Gohr { 289639d4c50SAndreas Gohr $sqlite = $this->getDB(); 290639d4c50SAndreas Gohr if (!$sqlite) return false; 291639d4c50SAndreas Gohr 292639d4c50SAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, C.ack FROM assignments A 293639d4c50SAndreas Gohr JOIN pages B 294639d4c50SAndreas Gohr ON A.page = B.page 295639d4c50SAndreas Gohr LEFT JOIN acks C 296639d4c50SAndreas Gohr ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) ) 297833123deSAnna Dabrowska WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees , ? , ?)"; 298833123deSAnna Dabrowska 299833123deSAnna Dabrowska if (!$includeDone) { 300833123deSAnna Dabrowska $sql .= ' AND ack IS NULL'; 301833123deSAnna Dabrowska } 302639d4c50SAndreas Gohr 303fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, $user, $user, implode('///', $groups)); 304639d4c50SAndreas Gohr } 305639d4c50SAndreas Gohr 306639d4c50SAndreas Gohr 307639d4c50SAndreas Gohr /** 308639d4c50SAndreas Gohr * Resolve names of users assigned to a given page 309639d4c50SAndreas Gohr * 310639d4c50SAndreas Gohr * This can be slow on huge user bases! 311639d4c50SAndreas Gohr * 312639d4c50SAndreas Gohr * @param string $page 313639d4c50SAndreas Gohr * @return array|false 314639d4c50SAndreas Gohr */ 315639d4c50SAndreas Gohr public function getPageAssignees($page) 316639d4c50SAndreas Gohr { 317639d4c50SAndreas Gohr $sqlite = $this->getDB(); 318639d4c50SAndreas Gohr if (!$sqlite) return false; 319639d4c50SAndreas Gohr /** @var AuthPlugin $auth */ 320639d4c50SAndreas Gohr global $auth; 321639d4c50SAndreas Gohr 322639d4c50SAndreas Gohr $sql = "SELECT pageassignees || ',' || autoassignees AS 'assignments' 323639d4c50SAndreas Gohr FROM assignments 324639d4c50SAndreas Gohr WHERE page = ?"; 325fea1a86fSAndreas Gohr $assignments = $sqlite->queryValue($sql, $page); 326639d4c50SAndreas Gohr 327639d4c50SAndreas Gohr $users = []; 328639d4c50SAndreas Gohr foreach (explode(',', $assignments) as $item) { 329639d4c50SAndreas Gohr $item = trim($item); 330639d4c50SAndreas Gohr if ($item === '') continue; 331639d4c50SAndreas Gohr if ($item[0] == '@') { 332639d4c50SAndreas Gohr $users = array_merge( 333639d4c50SAndreas Gohr $users, 334639d4c50SAndreas Gohr array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)])) 335639d4c50SAndreas Gohr ); 336639d4c50SAndreas Gohr } else { 337639d4c50SAndreas Gohr $users[] = $item; 338639d4c50SAndreas Gohr } 339639d4c50SAndreas Gohr } 340639d4c50SAndreas Gohr 341639d4c50SAndreas Gohr return array_unique($users); 342639d4c50SAndreas Gohr } 343639d4c50SAndreas Gohr 344639d4c50SAndreas Gohr // endregion 345639d4c50SAndreas Gohr // region Assignment Patterns 346639d4c50SAndreas Gohr 347639d4c50SAndreas Gohr /** 348f09444ffSAndreas Gohr * Get all the assignment patterns 349f09444ffSAndreas Gohr * @return array (pattern => assignees) 350f09444ffSAndreas Gohr */ 351f09444ffSAndreas Gohr public function getAssignmentPatterns() 352f09444ffSAndreas Gohr { 353f09444ffSAndreas Gohr $sqlite = $this->getDB(); 354f09444ffSAndreas Gohr if (!$sqlite) return []; 355f09444ffSAndreas Gohr 356f09444ffSAndreas Gohr $sql = "SELECT pattern, assignees FROM assignments_patterns"; 357fea1a86fSAndreas Gohr return $sqlite->queryKeyValueList($sql); 358f09444ffSAndreas Gohr } 359f09444ffSAndreas Gohr 360f09444ffSAndreas Gohr /** 361f09444ffSAndreas Gohr * Save new assignment patterns 362f09444ffSAndreas Gohr * 363f09444ffSAndreas Gohr * This resaves all patterns and reapplies them 364f09444ffSAndreas Gohr * 365f09444ffSAndreas Gohr * @param array $patterns (pattern => assignees) 366f09444ffSAndreas Gohr */ 367639d4c50SAndreas Gohr public function saveAssignmentPatterns($patterns) 368639d4c50SAndreas Gohr { 369f09444ffSAndreas Gohr $sqlite = $this->getDB(); 370f09444ffSAndreas Gohr if (!$sqlite) return; 371f09444ffSAndreas Gohr 372fea1a86fSAndreas Gohr $sqlite->getPdo()->beginTransaction(); 373fea1a86fSAndreas Gohr try { 374f09444ffSAndreas Gohr 375fea1a86fSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all assignments */ 376f09444ffSAndreas Gohr $sql = "UPDATE assignments SET autoassignees = ''"; 377fea1a86fSAndreas Gohr $sqlite->exec($sql); 378f09444ffSAndreas Gohr 379f09444ffSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all patterns */ 380f09444ffSAndreas Gohr $sql = "DELETE FROM assignments_patterns"; 381fea1a86fSAndreas Gohr $sqlite->exec($sql); 382f09444ffSAndreas Gohr 383f09444ffSAndreas Gohr // insert new patterns and gather affected pages 384f09444ffSAndreas Gohr $pages = []; 385f09444ffSAndreas Gohr 386f09444ffSAndreas Gohr $sql = "REPLACE INTO assignments_patterns (pattern, assignees) VALUES (?,?)"; 387f09444ffSAndreas Gohr foreach ($patterns as $pattern => $assignees) { 388f09444ffSAndreas Gohr $pattern = trim($pattern); 389f09444ffSAndreas Gohr $assignees = trim($assignees); 390f09444ffSAndreas Gohr if (!$pattern || !$assignees) continue; 391fea1a86fSAndreas Gohr $sqlite->exec($sql, [$pattern, $assignees]); 392f09444ffSAndreas Gohr 393f09444ffSAndreas Gohr // patterns may overlap, so we need to gather all affected pages first 394f09444ffSAndreas Gohr $affectedPages = $this->getPagesMatchingPattern($pattern); 395f09444ffSAndreas Gohr foreach ($affectedPages as $page) { 396f09444ffSAndreas Gohr if (isset($pages[$page])) { 397f09444ffSAndreas Gohr $pages[$page] .= ',' . $assignees; 398f09444ffSAndreas Gohr } else { 399f09444ffSAndreas Gohr $pages[$page] = $assignees; 400f09444ffSAndreas Gohr } 401f09444ffSAndreas Gohr } 402f09444ffSAndreas Gohr } 403f09444ffSAndreas Gohr 404f09444ffSAndreas Gohr $sql = "INSERT INTO assignments (page, autoassignees) VALUES (?, ?) 405f09444ffSAndreas Gohr ON CONFLICT(page) 406f09444ffSAndreas Gohr DO UPDATE SET autoassignees = ?"; 407f09444ffSAndreas Gohr foreach ($pages as $page => $assignees) { 408f09444ffSAndreas Gohr // remove duplicates and empty entries 4093b76424dSannda $assignees = implode(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 410fea1a86fSAndreas Gohr $sqlite->exec($sql, [$page, $assignees, $assignees]); 411f09444ffSAndreas Gohr } 412fea1a86fSAndreas Gohr } catch (Exception $e) { 413fea1a86fSAndreas Gohr $sqlite->getPdo()->rollBack(); 414fea1a86fSAndreas Gohr throw $e; 415fea1a86fSAndreas Gohr } 416fea1a86fSAndreas Gohr $sqlite->getPdo()->commit(); 417f09444ffSAndreas Gohr } 418f09444ffSAndreas Gohr 419f09444ffSAndreas Gohr /** 420f09444ffSAndreas Gohr * Get all known pages that match the given pattern 421f09444ffSAndreas Gohr * 422f09444ffSAndreas Gohr * @param $pattern 423f09444ffSAndreas Gohr * @return string[] 424f09444ffSAndreas Gohr */ 425639d4c50SAndreas Gohr public function getPagesMatchingPattern($pattern) 426639d4c50SAndreas Gohr { 427f09444ffSAndreas Gohr $sqlite = $this->getDB(); 428f09444ffSAndreas Gohr if (!$sqlite) return []; 429f09444ffSAndreas Gohr 430f09444ffSAndreas Gohr $sql = "SELECT page FROM pages WHERE MATCHES_PAGE_PATTERN(?, page)"; 431fea1a86fSAndreas Gohr $pages = $sqlite->queryAll($sql, $pattern); 432f09444ffSAndreas Gohr 433f09444ffSAndreas Gohr return array_column($pages, 'page'); 434f09444ffSAndreas Gohr } 435f09444ffSAndreas Gohr 436639d4c50SAndreas Gohr // endregion 437639d4c50SAndreas Gohr // region Acknowledgements 438ef3ab392SAndreas Gohr 439ef3ab392SAndreas Gohr /** 440ef3ab392SAndreas Gohr * Has the given user acknowledged the given page? 441ef3ab392SAndreas Gohr * 442ef3ab392SAndreas Gohr * @param string $page 443ef3ab392SAndreas Gohr * @param string $user 4445773dd37SAnna Dabrowska * @return bool|int timestamp of acknowledgement or false 445ef3ab392SAndreas Gohr */ 446ef3ab392SAndreas Gohr public function hasUserAcknowledged($page, $user) 447ef3ab392SAndreas Gohr { 448ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 449ef3ab392SAndreas Gohr if (!$sqlite) return false; 450ef3ab392SAndreas Gohr 451ef3ab392SAndreas Gohr $sql = "SELECT ack 452ef3ab392SAndreas Gohr FROM acks A, pages B 453ef3ab392SAndreas Gohr WHERE A.page = B.page 4545773dd37SAnna Dabrowska AND A.page = ? 4555773dd37SAnna Dabrowska AND A.user = ? 456ef3ab392SAndreas Gohr AND A.ack >= B.lastmod"; 457ef3ab392SAndreas Gohr 458fea1a86fSAndreas Gohr $acktime = $sqlite->queryValue($sql, $page, $user); 459ef3ab392SAndreas Gohr 460ef3ab392SAndreas Gohr return $acktime ? (int)$acktime : false; 461ef3ab392SAndreas Gohr } 4625773dd37SAnna Dabrowska 4635773dd37SAnna Dabrowska /** 464d9a8334dSAnna Dabrowska * Timestamp of the latest acknowledgment of the given page 465d9a8334dSAnna Dabrowska * by the given user 466d9a8334dSAnna Dabrowska * 467d9a8334dSAnna Dabrowska * @param string $page 468d9a8334dSAnna Dabrowska * @param string $user 469d9a8334dSAnna Dabrowska * @return bool|string 470d9a8334dSAnna Dabrowska */ 471d9a8334dSAnna Dabrowska public function getLatestUserAcknowledgement($page, $user) 472d9a8334dSAnna Dabrowska { 473d9a8334dSAnna Dabrowska $sqlite = $this->getDB(); 474d9a8334dSAnna Dabrowska if (!$sqlite) return false; 475d9a8334dSAnna Dabrowska 476d9a8334dSAnna Dabrowska $sql = "SELECT MAX(ack) 477d9a8334dSAnna Dabrowska FROM acks 478d9a8334dSAnna Dabrowska WHERE page = ? 479d9a8334dSAnna Dabrowska AND user = ?"; 480d9a8334dSAnna Dabrowska 481fea1a86fSAndreas Gohr return $sqlite->queryValue($sql, [$page, $user]); 482d9a8334dSAnna Dabrowska } 483d9a8334dSAnna Dabrowska 484d9a8334dSAnna Dabrowska /** 4855773dd37SAnna Dabrowska * Save user's acknowledgement for a given page 4865773dd37SAnna Dabrowska * 4875773dd37SAnna Dabrowska * @param string $page 4885773dd37SAnna Dabrowska * @param string $user 4895773dd37SAnna Dabrowska * @return bool 4905773dd37SAnna Dabrowska */ 4915773dd37SAnna Dabrowska public function saveAcknowledgement($page, $user) 4925773dd37SAnna Dabrowska { 4935773dd37SAnna Dabrowska $sqlite = $this->getDB(); 4945773dd37SAnna Dabrowska if (!$sqlite) return false; 4955773dd37SAnna Dabrowska 4968e55e483SAnna Dabrowska $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))"; 4975773dd37SAnna Dabrowska 498fea1a86fSAndreas Gohr $sqlite->exec($sql, $page, $user); 4995773dd37SAnna Dabrowska return true; 5005773dd37SAnna Dabrowska } 50174126d4bSAnna Dabrowska 50274126d4bSAnna Dabrowska /** 503863b6e48SAndreas Gohr * Get all pages a user needs to acknowledge and the last acknowledge date 504d6011abdSAnna Dabrowska * 505863b6e48SAndreas Gohr * @param string $user 506863b6e48SAndreas Gohr * @param array $groups 507d6011abdSAnna Dabrowska * @return array|bool 508d6011abdSAnna Dabrowska */ 509863b6e48SAndreas Gohr public function getUserAcknowledgements($user, $groups) 510d6011abdSAnna Dabrowska { 511d6011abdSAnna Dabrowska $sqlite = $this->getDB(); 512d6011abdSAnna Dabrowska if (!$sqlite) return false; 513d6011abdSAnna Dabrowska 514f09444ffSAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, MAX(C.ack) AS ack 515863b6e48SAndreas Gohr FROM assignments A 516863b6e48SAndreas Gohr JOIN pages B 517863b6e48SAndreas Gohr ON A.page = B.page 518863b6e48SAndreas Gohr LEFT JOIN acks C 519863b6e48SAndreas Gohr ON A.page = C.page AND C.user = ? 520f09444ffSAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees, ? , ?) 521863b6e48SAndreas Gohr GROUP BY A.page 522863b6e48SAndreas Gohr ORDER BY A.page 523863b6e48SAndreas Gohr "; 524863b6e48SAndreas Gohr 525fea1a86fSAndreas Gohr return $sqlite->queryAll($sql, [$user, $user, implode('///', $groups)]); 526863b6e48SAndreas Gohr } 527863b6e48SAndreas Gohr 528863b6e48SAndreas Gohr /** 529c6d8c1d9SAndreas Gohr * Get ack status for all assigned users of a given page 530c6d8c1d9SAndreas Gohr * 531c6d8c1d9SAndreas Gohr * This can be slow! 532c6d8c1d9SAndreas Gohr * 533c6d8c1d9SAndreas Gohr * @param string $page 534c6d8c1d9SAndreas Gohr * @return array|false 535c6d8c1d9SAndreas Gohr */ 536b6817aacSAndreas Gohr public function getPageAcknowledgements($page, $max = 0) 537c6d8c1d9SAndreas Gohr { 538c6d8c1d9SAndreas Gohr $users = $this->getPageAssignees($page); 539c6d8c1d9SAndreas Gohr if ($users === false) return false; 540c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 541c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 542c6d8c1d9SAndreas Gohr 5433b76424dSannda $ulist = implode(',', array_map([$sqlite->getPdo(), 'quote'], $users)); 544c6d8c1d9SAndreas Gohr $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack 545c6d8c1d9SAndreas Gohr FROM pages A 546c6d8c1d9SAndreas Gohr LEFT JOIN acks B 547c6d8c1d9SAndreas Gohr ON A.page = B.page 548c6d8c1d9SAndreas Gohr AND B.user IN ($ulist) 549c6d8c1d9SAndreas Gohr WHERE A.page = ? 550c6d8c1d9SAndreas Gohr GROUP BY A.page, B.user 551c6d8c1d9SAndreas Gohr "; 552b6817aacSAndreas Gohr if ($max) $sql .= " LIMIT $max"; 553fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $page); 554c6d8c1d9SAndreas Gohr 555c6d8c1d9SAndreas Gohr // there should be at least one result, unless the page is unknown 556c6d8c1d9SAndreas Gohr if (!count($acknowledgements)) return false; 557c6d8c1d9SAndreas Gohr 558c6d8c1d9SAndreas Gohr $baseinfo = [ 559c6d8c1d9SAndreas Gohr 'page' => $acknowledgements[0]['page'], 560c6d8c1d9SAndreas Gohr 'lastmod' => $acknowledgements[0]['lastmod'], 561c6d8c1d9SAndreas Gohr 'user' => null, 562c6d8c1d9SAndreas Gohr 'ack' => null, 563c6d8c1d9SAndreas Gohr ]; 564c6d8c1d9SAndreas Gohr 565c6d8c1d9SAndreas Gohr // fill up the result with all users that never acknowledged the page 566c6d8c1d9SAndreas Gohr $combined = []; 567c6d8c1d9SAndreas Gohr foreach ($acknowledgements as $ack) { 568c6d8c1d9SAndreas Gohr if ($ack['user'] !== null) { 569c6d8c1d9SAndreas Gohr $combined[$ack['user']] = $ack; 570c6d8c1d9SAndreas Gohr } 571c6d8c1d9SAndreas Gohr } 572c6d8c1d9SAndreas Gohr foreach ($users as $user) { 573c6d8c1d9SAndreas Gohr if (!isset($combined[$user])) { 574c6d8c1d9SAndreas Gohr $combined[$user] = array_merge($baseinfo, ['user' => $user]); 575c6d8c1d9SAndreas Gohr } 576c6d8c1d9SAndreas Gohr } 577c6d8c1d9SAndreas Gohr 578c6d8c1d9SAndreas Gohr ksort($combined); 579c6d8c1d9SAndreas Gohr return array_values($combined); 580c6d8c1d9SAndreas Gohr } 581c6d8c1d9SAndreas Gohr 582c6d8c1d9SAndreas Gohr /** 583863b6e48SAndreas Gohr * Returns all acknowledgements 584863b6e48SAndreas Gohr * 585863b6e48SAndreas Gohr * @param int $limit maximum number of results 586863b6e48SAndreas Gohr * @return array|bool 587863b6e48SAndreas Gohr */ 588863b6e48SAndreas Gohr public function getAcknowledgements($limit = 100) 589863b6e48SAndreas Gohr { 590863b6e48SAndreas Gohr $sqlite = $this->getDB(); 591863b6e48SAndreas Gohr if (!$sqlite) return false; 592863b6e48SAndreas Gohr 593863b6e48SAndreas Gohr $sql = ' 59484db77b6SAndreas Gohr SELECT A.page, A.user, B.lastmod, max(A.ack) AS ack 59584db77b6SAndreas Gohr FROM acks A, pages B 59684db77b6SAndreas Gohr WHERE A.page = B.page 59784db77b6SAndreas Gohr GROUP BY A.user, A.page 598863b6e48SAndreas Gohr ORDER BY ack DESC 599863b6e48SAndreas Gohr LIMIT ? 600863b6e48SAndreas Gohr '; 601fea1a86fSAndreas Gohr $acknowledgements = $sqlite->queryAll($sql, $limit); 602d6011abdSAnna Dabrowska 603d6011abdSAnna Dabrowska return $acknowledgements; 604d6011abdSAnna Dabrowska } 605f09444ffSAndreas Gohr 606639d4c50SAndreas Gohr // endregion 6074d6d17d0SAndreas Gohr} 608