14d6d17d0SAndreas Gohr<?php 2c6d8c1d9SAndreas Gohr 3c6d8c1d9SAndreas Gohruse dokuwiki\Extension\AuthPlugin; 4c6d8c1d9SAndreas Gohr 54d6d17d0SAndreas Gohr/** 64d6d17d0SAndreas Gohr * DokuWiki Plugin acknowledge (Helper Component) 74d6d17d0SAndreas Gohr * 84d6d17d0SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 94d6d17d0SAndreas Gohr * @author Andreas Gohr, Anna Dabrowska <dokuwiki@cosmocode.de> 104d6d17d0SAndreas Gohr */ 114d6d17d0SAndreas Gohrclass helper_plugin_acknowledge extends DokuWiki_Plugin 124d6d17d0SAndreas Gohr{ 134d6d17d0SAndreas Gohr 14*639d4c50SAndreas Gohr // region Database Management 15*639d4c50SAndreas Gohr 16cabb51d3SAndreas Gohr /** 17cabb51d3SAndreas Gohr * @return helper_plugin_sqlite|null 18cabb51d3SAndreas Gohr */ 19cabb51d3SAndreas Gohr public function getDB() 20cabb51d3SAndreas Gohr { 21cabb51d3SAndreas Gohr /** @var \helper_plugin_sqlite $sqlite */ 22cabb51d3SAndreas Gohr $sqlite = plugin_load('helper', 'sqlite'); 23cabb51d3SAndreas Gohr if ($sqlite === null) { 24cabb51d3SAndreas Gohr msg($this->getLang('error sqlite plugin missing'), -1); 25cabb51d3SAndreas Gohr return null; 26cabb51d3SAndreas Gohr } 27f09444ffSAndreas Gohr $sqlite->getAdapter()->setUseNativeAlter(true); 28cabb51d3SAndreas Gohr if (!$sqlite->init('acknowledgement', __DIR__ . '/db')) { 29cabb51d3SAndreas Gohr return null; 30cabb51d3SAndreas Gohr } 31cabb51d3SAndreas Gohr 329c3eae1eSAnna Dabrowska $this->registerUDF($sqlite); 339c3eae1eSAnna Dabrowska 34cabb51d3SAndreas Gohr return $sqlite; 35cabb51d3SAndreas Gohr } 36cabb51d3SAndreas Gohr 37cabb51d3SAndreas Gohr /** 389c3eae1eSAnna Dabrowska * Register user defined functions 399c3eae1eSAnna Dabrowska * 409c3eae1eSAnna Dabrowska * @param helper_plugin_sqlite $sqlite 419c3eae1eSAnna Dabrowska */ 429c3eae1eSAnna Dabrowska protected function registerUDF($sqlite) 439c3eae1eSAnna Dabrowska { 449c3eae1eSAnna Dabrowska $sqlite->create_function('AUTH_ISMEMBER', [$this, 'auth_isMember'], -1); 45f09444ffSAndreas Gohr $sqlite->create_function('MATCHES_PAGE_PATTERN', [$this, 'matchPagePattern'], 2); 469c3eae1eSAnna Dabrowska } 479c3eae1eSAnna Dabrowska 489c3eae1eSAnna Dabrowska /** 499c3eae1eSAnna Dabrowska * Wrapper function for auth_isMember which accepts groups as string 509c3eae1eSAnna Dabrowska * 519c3eae1eSAnna Dabrowska * @param string $memberList 529c3eae1eSAnna Dabrowska * @param string $user 539c3eae1eSAnna Dabrowska * @param string $groups 549c3eae1eSAnna Dabrowska * @return bool 559c3eae1eSAnna Dabrowska */ 569c3eae1eSAnna Dabrowska public function auth_isMember($memberList, $user, $groups) 579c3eae1eSAnna Dabrowska { 5895113ed8SAnna Dabrowska return auth_isMember($memberList, $user, explode('///', $groups)); 599c3eae1eSAnna Dabrowska } 609c3eae1eSAnna Dabrowska 619c3eae1eSAnna Dabrowska /** 62*639d4c50SAndreas Gohr * Fills the page index with all unknown pages from the fulltext index 63*639d4c50SAndreas Gohr * @return void 64*639d4c50SAndreas Gohr */ 65*639d4c50SAndreas Gohr public function updatePageIndex() 66*639d4c50SAndreas Gohr { 67*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 68*639d4c50SAndreas Gohr if (!$sqlite) return; 69*639d4c50SAndreas Gohr 70*639d4c50SAndreas Gohr $pages = idx_getIndex('page', ''); 71*639d4c50SAndreas Gohr $sql = "INSERT OR IGNORE INTO pages (page, lastmod) VALUES (?,?)"; 72*639d4c50SAndreas Gohr 73*639d4c50SAndreas Gohr $sqlite->query('BEGIN TRANSACTION'); 74*639d4c50SAndreas Gohr foreach ($pages as $page) { 75*639d4c50SAndreas Gohr $page = trim($page); 76*639d4c50SAndreas Gohr $lastmod = @filemtime(wikiFN($page)); 77*639d4c50SAndreas Gohr if ($lastmod) { 78*639d4c50SAndreas Gohr $sqlite->query($sql, $page, $lastmod); 79*639d4c50SAndreas Gohr } 80*639d4c50SAndreas Gohr } 81*639d4c50SAndreas Gohr $sqlite->query('COMMIT TRANSACTION'); 82*639d4c50SAndreas Gohr } 83*639d4c50SAndreas Gohr 84*639d4c50SAndreas Gohr /** 85*639d4c50SAndreas Gohr * Check if the given pattern matches the given page 86*639d4c50SAndreas Gohr * 87*639d4c50SAndreas Gohr * @param string $pattern the pattern to check against 88*639d4c50SAndreas Gohr * @param string $page the cleaned pageid to check 89*639d4c50SAndreas Gohr * @return bool 90*639d4c50SAndreas Gohr */ 91*639d4c50SAndreas Gohr public function matchPagePattern($pattern, $page) 92*639d4c50SAndreas Gohr { 93*639d4c50SAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 94*639d4c50SAndreas Gohr 95*639d4c50SAndreas Gohr // regex patterns 96*639d4c50SAndreas Gohr if ($pattern[0] == '/') { 97*639d4c50SAndreas Gohr return (bool)preg_match($pattern, ":$page"); 98*639d4c50SAndreas Gohr } 99*639d4c50SAndreas Gohr 100*639d4c50SAndreas Gohr $pns = ':' . getNS($page) . ':'; 101*639d4c50SAndreas Gohr 102*639d4c50SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 103*639d4c50SAndreas Gohr if (substr($pattern, -2) == '**') { 104*639d4c50SAndreas Gohr // upper namespaces match 105*639d4c50SAndreas Gohr if (strpos($pns, $ans) === 0) { 106*639d4c50SAndreas Gohr return true; 107*639d4c50SAndreas Gohr } 108*639d4c50SAndreas Gohr } elseif (substr($pattern, -1) == '*') { 109*639d4c50SAndreas Gohr // namespaces match exact 110*639d4c50SAndreas Gohr if ($ans == $pns) { 111*639d4c50SAndreas Gohr return true; 112*639d4c50SAndreas Gohr } 113*639d4c50SAndreas Gohr } else { 114*639d4c50SAndreas Gohr // exact match 115*639d4c50SAndreas Gohr if (cleanID($pattern) == $page) { 116*639d4c50SAndreas Gohr return true; 117*639d4c50SAndreas Gohr } 118*639d4c50SAndreas Gohr } 119*639d4c50SAndreas Gohr 120*639d4c50SAndreas Gohr return false; 121*639d4c50SAndreas Gohr } 122*639d4c50SAndreas Gohr 123*639d4c50SAndreas Gohr // endregion 124*639d4c50SAndreas Gohr // region Page Data 125*639d4c50SAndreas Gohr 126*639d4c50SAndreas 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 = ?"; 139ef3ab392SAndreas Gohr $sqlite->query($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 { 150ed4e8871SAnna Dabrowska $changelog = new \dokuwiki\ChangeLog\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 (?,?)"; 162ef3ab392SAndreas Gohr $sqlite->query($sql, $page, $lastmod); 163ef3ab392SAndreas Gohr } 164ef3ab392SAndreas Gohr 165*639d4c50SAndreas Gohr // endregion 166*639d4c50SAndreas Gohr // region Assignments 167*639d4c50SAndreas 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 = ?"; 179f09444ffSAndreas Gohr $sqlite->query($sql, $page); 180f09444ffSAndreas Gohr } 181f09444ffSAndreas Gohr 182f09444ffSAndreas Gohr /** 183*639d4c50SAndreas Gohr * Set assignees for a given page as manually specified 184*639d4c50SAndreas Gohr * 185*639d4c50SAndreas Gohr * @param string $page Page ID 186*639d4c50SAndreas Gohr * @param string $assignees 187*639d4c50SAndreas Gohr * @return void 188*639d4c50SAndreas Gohr */ 189*639d4c50SAndreas Gohr public function setPageAssignees($page, $assignees) 190*639d4c50SAndreas Gohr { 191*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 192*639d4c50SAndreas Gohr if (!$sqlite) return; 193*639d4c50SAndreas Gohr 194*639d4c50SAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 195*639d4c50SAndreas Gohr 196*639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'pageassignees') VALUES (?,?)"; 197*639d4c50SAndreas Gohr $sqlite->query($sql, $page, $assignees); 198*639d4c50SAndreas Gohr } 199*639d4c50SAndreas Gohr 200*639d4c50SAndreas Gohr /** 201*639d4c50SAndreas Gohr * Set assignees for a given page from the patterns 202*639d4c50SAndreas Gohr * @param string $page Page ID 203*639d4c50SAndreas Gohr */ 204*639d4c50SAndreas Gohr public function setAutoAssignees($page) 205*639d4c50SAndreas Gohr { 206*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 207*639d4c50SAndreas Gohr if (!$sqlite) return; 208*639d4c50SAndreas Gohr 209*639d4c50SAndreas Gohr $patterns = $this->getAssignmentPatterns(); 210*639d4c50SAndreas Gohr 211*639d4c50SAndreas Gohr // given assignees 212*639d4c50SAndreas Gohr $assignees = ''; 213*639d4c50SAndreas Gohr 214*639d4c50SAndreas Gohr // find all patterns that match the page and add the configured assignees 215*639d4c50SAndreas Gohr foreach ($patterns as $pattern => $assignees) { 216*639d4c50SAndreas Gohr if ($this->matchPagePattern($pattern, $page)) { 217*639d4c50SAndreas Gohr $assignees .= ',' . $assignees; 218*639d4c50SAndreas Gohr } 219*639d4c50SAndreas Gohr } 220*639d4c50SAndreas Gohr 221*639d4c50SAndreas Gohr // remove duplicates and empty entries 222*639d4c50SAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 223*639d4c50SAndreas Gohr 224*639d4c50SAndreas Gohr // store the assignees 225*639d4c50SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'autoassignees') VALUES (?,?)"; 226*639d4c50SAndreas Gohr $sqlite->query($sql, $page, $assignees); 227*639d4c50SAndreas Gohr } 228*639d4c50SAndreas Gohr 229*639d4c50SAndreas Gohr /** 230*639d4c50SAndreas Gohr * Is the given user one of the assignees for this page 231*639d4c50SAndreas Gohr * 232*639d4c50SAndreas Gohr * @param string $page Page ID 233*639d4c50SAndreas Gohr * @param string $user user name to check 234*639d4c50SAndreas Gohr * @param string[] $groups groups this user is in 235*639d4c50SAndreas Gohr * @return bool 236*639d4c50SAndreas Gohr */ 237*639d4c50SAndreas Gohr public function isUserAssigned($page, $user, $groups) 238*639d4c50SAndreas Gohr { 239*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 240*639d4c50SAndreas Gohr if (!$sqlite) return false; 241*639d4c50SAndreas Gohr 242*639d4c50SAndreas Gohr $sql = "SELECT pageassignees,autoassignees FROM assignments WHERE page = ?"; 243*639d4c50SAndreas Gohr $result = $sqlite->query($sql, $page); 244*639d4c50SAndreas Gohr $row = $sqlite->res2row($result); 245*639d4c50SAndreas Gohr $sqlite->res_close($result); 246*639d4c50SAndreas Gohr $assignees = $row['pageassignees'] . ',' . $row['autoassignees']; 247*639d4c50SAndreas Gohr return auth_isMember($assignees, $user, $groups); 248*639d4c50SAndreas Gohr } 249*639d4c50SAndreas Gohr 250*639d4c50SAndreas Gohr /** 251*639d4c50SAndreas Gohr * Fetch all assignments for a given user, with additional page information, 252*639d4c50SAndreas Gohr * filtering already granted acknowledgements. 253*639d4c50SAndreas Gohr * 254*639d4c50SAndreas Gohr * @param string $user 255*639d4c50SAndreas Gohr * @param array $groups 256*639d4c50SAndreas Gohr * @return array|bool 257*639d4c50SAndreas Gohr */ 258*639d4c50SAndreas Gohr public function getUserAssignments($user, $groups) 259*639d4c50SAndreas Gohr { 260*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 261*639d4c50SAndreas Gohr if (!$sqlite) return false; 262*639d4c50SAndreas Gohr 263*639d4c50SAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, C.ack FROM assignments A 264*639d4c50SAndreas Gohr JOIN pages B 265*639d4c50SAndreas Gohr ON A.page = B.page 266*639d4c50SAndreas Gohr LEFT JOIN acks C 267*639d4c50SAndreas Gohr ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) ) 268*639d4c50SAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees , ? , ?) 269*639d4c50SAndreas Gohr AND ack IS NULL"; 270*639d4c50SAndreas Gohr 271*639d4c50SAndreas Gohr $result = $sqlite->query($sql, $user, $user, implode('///', $groups)); 272*639d4c50SAndreas Gohr $assignments = $sqlite->res2arr($result); 273*639d4c50SAndreas Gohr $sqlite->res_close($result); 274*639d4c50SAndreas Gohr 275*639d4c50SAndreas Gohr return $assignments; 276*639d4c50SAndreas Gohr } 277*639d4c50SAndreas Gohr 278*639d4c50SAndreas Gohr 279*639d4c50SAndreas Gohr /** 280*639d4c50SAndreas Gohr * Resolve names of users assigned to a given page 281*639d4c50SAndreas Gohr * 282*639d4c50SAndreas Gohr * This can be slow on huge user bases! 283*639d4c50SAndreas Gohr * 284*639d4c50SAndreas Gohr * @param string $page 285*639d4c50SAndreas Gohr * @return array|false 286*639d4c50SAndreas Gohr */ 287*639d4c50SAndreas Gohr public function getPageAssignees($page) 288*639d4c50SAndreas Gohr { 289*639d4c50SAndreas Gohr $sqlite = $this->getDB(); 290*639d4c50SAndreas Gohr if (!$sqlite) return false; 291*639d4c50SAndreas Gohr /** @var AuthPlugin $auth */ 292*639d4c50SAndreas Gohr global $auth; 293*639d4c50SAndreas Gohr 294*639d4c50SAndreas Gohr $sql = "SELECT pageassignees || ',' || autoassignees AS 'assignments' 295*639d4c50SAndreas Gohr FROM assignments 296*639d4c50SAndreas Gohr WHERE page = ?"; 297*639d4c50SAndreas Gohr $result = $sqlite->query($sql, $page); 298*639d4c50SAndreas Gohr $assignments = $sqlite->res2single($result); 299*639d4c50SAndreas Gohr $sqlite->res_close($result); 300*639d4c50SAndreas Gohr 301*639d4c50SAndreas Gohr $users = []; 302*639d4c50SAndreas Gohr foreach (explode(',', $assignments) as $item) { 303*639d4c50SAndreas Gohr $item = trim($item); 304*639d4c50SAndreas Gohr if ($item === '') continue; 305*639d4c50SAndreas Gohr if ($item[0] == '@') { 306*639d4c50SAndreas Gohr $users = array_merge( 307*639d4c50SAndreas Gohr $users, 308*639d4c50SAndreas Gohr array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)])) 309*639d4c50SAndreas Gohr ); 310*639d4c50SAndreas Gohr } else { 311*639d4c50SAndreas Gohr $users[] = $item; 312*639d4c50SAndreas Gohr } 313*639d4c50SAndreas Gohr } 314*639d4c50SAndreas Gohr 315*639d4c50SAndreas Gohr return array_unique($users); 316*639d4c50SAndreas Gohr } 317*639d4c50SAndreas Gohr 318*639d4c50SAndreas Gohr // endregion 319*639d4c50SAndreas Gohr // region Assignment Patterns 320*639d4c50SAndreas Gohr 321*639d4c50SAndreas Gohr /** 322f09444ffSAndreas Gohr * Get all the assignment patterns 323f09444ffSAndreas Gohr * @return array (pattern => assignees) 324f09444ffSAndreas Gohr */ 325f09444ffSAndreas Gohr public function getAssignmentPatterns() 326f09444ffSAndreas Gohr { 327f09444ffSAndreas Gohr $sqlite = $this->getDB(); 328f09444ffSAndreas Gohr if (!$sqlite) return []; 329f09444ffSAndreas Gohr 330f09444ffSAndreas Gohr $sql = "SELECT pattern, assignees FROM assignments_patterns"; 331f09444ffSAndreas Gohr $result = $sqlite->query($sql); 332f09444ffSAndreas Gohr $patterns = $sqlite->res2arr($result); 333f09444ffSAndreas Gohr $sqlite->res_close($result); 334f09444ffSAndreas Gohr 335f09444ffSAndreas Gohr return array_combine( 336f09444ffSAndreas Gohr array_column($patterns, 'pattern'), 337f09444ffSAndreas Gohr array_column($patterns, 'assignees') 338f09444ffSAndreas Gohr ); 339f09444ffSAndreas Gohr } 340f09444ffSAndreas Gohr 341f09444ffSAndreas Gohr /** 342f09444ffSAndreas Gohr * Save new assignment patterns 343f09444ffSAndreas Gohr * 344f09444ffSAndreas Gohr * This resaves all patterns and reapplies them 345f09444ffSAndreas Gohr * 346f09444ffSAndreas Gohr * @param array $patterns (pattern => assignees) 347f09444ffSAndreas Gohr */ 348*639d4c50SAndreas Gohr public function saveAssignmentPatterns($patterns) 349*639d4c50SAndreas Gohr { 350f09444ffSAndreas Gohr $sqlite = $this->getDB(); 351f09444ffSAndreas Gohr if (!$sqlite) return; 352f09444ffSAndreas Gohr 353f09444ffSAndreas Gohr $sqlite->query('BEGIN TRANSACTION'); 354f09444ffSAndreas Gohr 355f09444ffSAndreas Gohr /** @noinsp0ection SqlWithoutWhere Remove all assignments */ 356f09444ffSAndreas Gohr $sql = "UPDATE assignments SET autoassignees = ''"; 357f09444ffSAndreas Gohr $sqlite->query($sql); 358f09444ffSAndreas Gohr 359f09444ffSAndreas Gohr /** @noinspection SqlWithoutWhere Remove all patterns */ 360f09444ffSAndreas Gohr $sql = "DELETE FROM assignments_patterns"; 361f09444ffSAndreas Gohr $sqlite->query($sql); 362f09444ffSAndreas Gohr 363f09444ffSAndreas Gohr // insert new patterns and gather affected pages 364f09444ffSAndreas Gohr $pages = []; 365f09444ffSAndreas Gohr 366f09444ffSAndreas Gohr $sql = "REPLACE INTO assignments_patterns (pattern, assignees) VALUES (?,?)"; 367f09444ffSAndreas Gohr foreach ($patterns as $pattern => $assignees) { 368f09444ffSAndreas Gohr $pattern = trim($pattern); 369f09444ffSAndreas Gohr $assignees = trim($assignees); 370f09444ffSAndreas Gohr if (!$pattern || !$assignees) continue; 371f09444ffSAndreas Gohr $sqlite->query($sql, $pattern, $assignees); 372f09444ffSAndreas Gohr 373f09444ffSAndreas Gohr // patterns may overlap, so we need to gather all affected pages first 374f09444ffSAndreas Gohr $affectedPages = $this->getPagesMatchingPattern($pattern); 375f09444ffSAndreas Gohr foreach ($affectedPages as $page) { 376f09444ffSAndreas Gohr if (isset($pages[$page])) { 377f09444ffSAndreas Gohr $pages[$page] .= ',' . $assignees; 378f09444ffSAndreas Gohr } else { 379f09444ffSAndreas Gohr $pages[$page] = $assignees; 380f09444ffSAndreas Gohr } 381f09444ffSAndreas Gohr } 382f09444ffSAndreas Gohr } 383f09444ffSAndreas Gohr 384f09444ffSAndreas Gohr $sql = "INSERT INTO assignments (page, autoassignees) VALUES (?, ?) 385f09444ffSAndreas Gohr ON CONFLICT(page) 386f09444ffSAndreas Gohr DO UPDATE SET autoassignees = ?"; 387f09444ffSAndreas Gohr foreach ($pages as $page => $assignees) { 388f09444ffSAndreas Gohr // remove duplicates and empty entries 389f09444ffSAndreas Gohr $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees))))); 390f09444ffSAndreas Gohr $sqlite->query($sql, $page, $assignees, $assignees); 391f09444ffSAndreas Gohr } 392f09444ffSAndreas Gohr 393f09444ffSAndreas Gohr $sqlite->query('COMMIT TRANSACTION'); 394f09444ffSAndreas Gohr } 395f09444ffSAndreas Gohr 396f09444ffSAndreas Gohr /** 397f09444ffSAndreas Gohr * Get all known pages that match the given pattern 398f09444ffSAndreas Gohr * 399f09444ffSAndreas Gohr * @param $pattern 400f09444ffSAndreas Gohr * @return string[] 401f09444ffSAndreas Gohr */ 402*639d4c50SAndreas Gohr public function getPagesMatchingPattern($pattern) 403*639d4c50SAndreas Gohr { 404f09444ffSAndreas Gohr $sqlite = $this->getDB(); 405f09444ffSAndreas Gohr if (!$sqlite) return []; 406f09444ffSAndreas Gohr 407f09444ffSAndreas Gohr $sql = "SELECT page FROM pages WHERE MATCHES_PAGE_PATTERN(?, page)"; 408f09444ffSAndreas Gohr $result = $sqlite->query($sql, $pattern); 409f09444ffSAndreas Gohr $pages = $sqlite->res2arr($result); 410f09444ffSAndreas Gohr $sqlite->res_close($result); 411f09444ffSAndreas Gohr 412f09444ffSAndreas Gohr return array_column($pages, 'page'); 413f09444ffSAndreas Gohr } 414f09444ffSAndreas Gohr 415*639d4c50SAndreas Gohr // endregion 416*639d4c50SAndreas Gohr // region Acknowledgements 417ef3ab392SAndreas Gohr 418ef3ab392SAndreas Gohr /** 419ef3ab392SAndreas Gohr * Has the given user acknowledged the given page? 420ef3ab392SAndreas Gohr * 421ef3ab392SAndreas Gohr * @param string $page 422ef3ab392SAndreas Gohr * @param string $user 4235773dd37SAnna Dabrowska * @return bool|int timestamp of acknowledgement or false 424ef3ab392SAndreas Gohr */ 425ef3ab392SAndreas Gohr public function hasUserAcknowledged($page, $user) 426ef3ab392SAndreas Gohr { 427ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 428ef3ab392SAndreas Gohr if (!$sqlite) return false; 429ef3ab392SAndreas Gohr 430ef3ab392SAndreas Gohr $sql = "SELECT ack 431ef3ab392SAndreas Gohr FROM acks A, pages B 432ef3ab392SAndreas Gohr WHERE A.page = B.page 4335773dd37SAnna Dabrowska AND A.page = ? 4345773dd37SAnna Dabrowska AND A.user = ? 435ef3ab392SAndreas Gohr AND A.ack >= B.lastmod"; 436ef3ab392SAndreas Gohr 437ef3ab392SAndreas Gohr $result = $sqlite->query($sql, $page, $user); 438ef3ab392SAndreas Gohr $acktime = $sqlite->res2single($result); 439ef3ab392SAndreas Gohr $sqlite->res_close($result); 440ef3ab392SAndreas Gohr 441ef3ab392SAndreas Gohr return $acktime ? (int)$acktime : false; 442ef3ab392SAndreas Gohr } 4435773dd37SAnna Dabrowska 4445773dd37SAnna Dabrowska /** 445d9a8334dSAnna Dabrowska * Timestamp of the latest acknowledgment of the given page 446d9a8334dSAnna Dabrowska * by the given user 447d9a8334dSAnna Dabrowska * 448d9a8334dSAnna Dabrowska * @param string $page 449d9a8334dSAnna Dabrowska * @param string $user 450d9a8334dSAnna Dabrowska * @return bool|string 451d9a8334dSAnna Dabrowska */ 452d9a8334dSAnna Dabrowska public function getLatestUserAcknowledgement($page, $user) 453d9a8334dSAnna Dabrowska { 454d9a8334dSAnna Dabrowska $sqlite = $this->getDB(); 455d9a8334dSAnna Dabrowska if (!$sqlite) return false; 456d9a8334dSAnna Dabrowska 457d9a8334dSAnna Dabrowska $sql = "SELECT MAX(ack) 458d9a8334dSAnna Dabrowska FROM acks 459d9a8334dSAnna Dabrowska WHERE page = ? 460d9a8334dSAnna Dabrowska AND user = ?"; 461d9a8334dSAnna Dabrowska 462d9a8334dSAnna Dabrowska $result = $sqlite->query($sql, $page, $user); 463d9a8334dSAnna Dabrowska $latestAck = $sqlite->res2single($result); 464d9a8334dSAnna Dabrowska $sqlite->res_close($result); 465d9a8334dSAnna Dabrowska 466d9a8334dSAnna Dabrowska return $latestAck; 467d9a8334dSAnna Dabrowska } 468d9a8334dSAnna Dabrowska 469d9a8334dSAnna Dabrowska /** 4705773dd37SAnna Dabrowska * Save user's acknowledgement for a given page 4715773dd37SAnna Dabrowska * 4725773dd37SAnna Dabrowska * @param string $page 4735773dd37SAnna Dabrowska * @param string $user 4745773dd37SAnna Dabrowska * @return bool 4755773dd37SAnna Dabrowska */ 4765773dd37SAnna Dabrowska public function saveAcknowledgement($page, $user) 4775773dd37SAnna Dabrowska { 4785773dd37SAnna Dabrowska $sqlite = $this->getDB(); 4795773dd37SAnna Dabrowska if (!$sqlite) return false; 4805773dd37SAnna Dabrowska 4818e55e483SAnna Dabrowska $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))"; 4825773dd37SAnna Dabrowska 4835773dd37SAnna Dabrowska $result = $sqlite->query($sql, $page, $user); 4845773dd37SAnna Dabrowska $sqlite->res_close($result); 4855773dd37SAnna Dabrowska return true; 4865773dd37SAnna Dabrowska 4875773dd37SAnna Dabrowska } 48874126d4bSAnna Dabrowska 48974126d4bSAnna Dabrowska /** 490863b6e48SAndreas Gohr * Get all pages a user needs to acknowledge and the last acknowledge date 491d6011abdSAnna Dabrowska * 492863b6e48SAndreas Gohr * @param string $user 493863b6e48SAndreas Gohr * @param array $groups 494d6011abdSAnna Dabrowska * @return array|bool 495d6011abdSAnna Dabrowska */ 496863b6e48SAndreas Gohr public function getUserAcknowledgements($user, $groups) 497d6011abdSAnna Dabrowska { 498d6011abdSAnna Dabrowska $sqlite = $this->getDB(); 499d6011abdSAnna Dabrowska if (!$sqlite) return false; 500d6011abdSAnna Dabrowska 501f09444ffSAndreas Gohr $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, MAX(C.ack) AS ack 502863b6e48SAndreas Gohr FROM assignments A 503863b6e48SAndreas Gohr JOIN pages B 504863b6e48SAndreas Gohr ON A.page = B.page 505863b6e48SAndreas Gohr LEFT JOIN acks C 506863b6e48SAndreas Gohr ON A.page = C.page AND C.user = ? 507f09444ffSAndreas Gohr WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees, ? , ?) 508863b6e48SAndreas Gohr GROUP BY A.page 509863b6e48SAndreas Gohr ORDER BY A.page 510863b6e48SAndreas Gohr "; 511863b6e48SAndreas Gohr 512863b6e48SAndreas Gohr $result = $sqlite->query($sql, $user, $user, implode('///', $groups)); 513863b6e48SAndreas Gohr $assignments = $sqlite->res2arr($result); 514863b6e48SAndreas Gohr $sqlite->res_close($result); 515863b6e48SAndreas Gohr 516863b6e48SAndreas Gohr return $assignments; 517863b6e48SAndreas Gohr } 518863b6e48SAndreas Gohr 519863b6e48SAndreas Gohr /** 520c6d8c1d9SAndreas Gohr * Get ack status for all assigned users of a given page 521c6d8c1d9SAndreas Gohr * 522c6d8c1d9SAndreas Gohr * This can be slow! 523c6d8c1d9SAndreas Gohr * 524c6d8c1d9SAndreas Gohr * @param string $page 525c6d8c1d9SAndreas Gohr * @return array|false 526c6d8c1d9SAndreas Gohr */ 527c6d8c1d9SAndreas Gohr public function getPageAcknowledgements($page) 528c6d8c1d9SAndreas Gohr { 529c6d8c1d9SAndreas Gohr $users = $this->getPageAssignees($page); 530c6d8c1d9SAndreas Gohr if ($users === false) return false; 531c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 532c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 533c6d8c1d9SAndreas Gohr 534c6d8c1d9SAndreas Gohr $ulist = $sqlite->quote_and_join($users); 535c6d8c1d9SAndreas Gohr $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack 536c6d8c1d9SAndreas Gohr FROM pages A 537c6d8c1d9SAndreas Gohr LEFT JOIN acks B 538c6d8c1d9SAndreas Gohr ON A.page = B.page 539c6d8c1d9SAndreas Gohr AND B.user IN ($ulist) 540c6d8c1d9SAndreas Gohr WHERE A.page = ? 541c6d8c1d9SAndreas Gohr GROUP BY A.page, B.user 542c6d8c1d9SAndreas Gohr "; 543c6d8c1d9SAndreas Gohr $result = $sqlite->query($sql, $page); 544c6d8c1d9SAndreas Gohr $acknowledgements = $sqlite->res2arr($result); 545c6d8c1d9SAndreas Gohr $sqlite->res_close($result); 546c6d8c1d9SAndreas Gohr 547c6d8c1d9SAndreas Gohr // there should be at least one result, unless the page is unknown 548c6d8c1d9SAndreas Gohr if (!count($acknowledgements)) return false; 549c6d8c1d9SAndreas Gohr 550c6d8c1d9SAndreas Gohr $baseinfo = [ 551c6d8c1d9SAndreas Gohr 'page' => $acknowledgements[0]['page'], 552c6d8c1d9SAndreas Gohr 'lastmod' => $acknowledgements[0]['lastmod'], 553c6d8c1d9SAndreas Gohr 'user' => null, 554c6d8c1d9SAndreas Gohr 'ack' => null, 555c6d8c1d9SAndreas Gohr ]; 556c6d8c1d9SAndreas Gohr 557c6d8c1d9SAndreas Gohr // fill up the result with all users that never acknowledged the page 558c6d8c1d9SAndreas Gohr $combined = []; 559c6d8c1d9SAndreas Gohr foreach ($acknowledgements as $ack) { 560c6d8c1d9SAndreas Gohr if ($ack['user'] !== null) { 561c6d8c1d9SAndreas Gohr $combined[$ack['user']] = $ack; 562c6d8c1d9SAndreas Gohr } 563c6d8c1d9SAndreas Gohr } 564c6d8c1d9SAndreas Gohr foreach ($users as $user) { 565c6d8c1d9SAndreas Gohr if (!isset($combined[$user])) { 566c6d8c1d9SAndreas Gohr $combined[$user] = array_merge($baseinfo, ['user' => $user]); 567c6d8c1d9SAndreas Gohr } 568c6d8c1d9SAndreas Gohr } 569c6d8c1d9SAndreas Gohr 570c6d8c1d9SAndreas Gohr ksort($combined); 571c6d8c1d9SAndreas Gohr return array_values($combined); 572c6d8c1d9SAndreas Gohr } 573c6d8c1d9SAndreas Gohr 574c6d8c1d9SAndreas Gohr /** 575863b6e48SAndreas Gohr * Returns all acknowledgements 576863b6e48SAndreas Gohr * 577863b6e48SAndreas Gohr * @param int $limit maximum number of results 578863b6e48SAndreas Gohr * @return array|bool 579863b6e48SAndreas Gohr */ 580863b6e48SAndreas Gohr public function getAcknowledgements($limit = 100) 581863b6e48SAndreas Gohr { 582863b6e48SAndreas Gohr $sqlite = $this->getDB(); 583863b6e48SAndreas Gohr if (!$sqlite) return false; 584863b6e48SAndreas Gohr 585863b6e48SAndreas Gohr $sql = ' 58684db77b6SAndreas Gohr SELECT A.page, A.user, B.lastmod, max(A.ack) AS ack 58784db77b6SAndreas Gohr FROM acks A, pages B 58884db77b6SAndreas Gohr WHERE A.page = B.page 58984db77b6SAndreas Gohr GROUP BY A.user, A.page 590863b6e48SAndreas Gohr ORDER BY ack DESC 591863b6e48SAndreas Gohr LIMIT ? 592863b6e48SAndreas Gohr '; 593863b6e48SAndreas Gohr $result = $sqlite->query($sql, $limit); 594d6011abdSAnna Dabrowska $acknowledgements = $sqlite->res2arr($result); 595d6011abdSAnna Dabrowska $sqlite->res_close($result); 596d6011abdSAnna Dabrowska 597d6011abdSAnna Dabrowska return $acknowledgements; 598d6011abdSAnna Dabrowska } 599f09444ffSAndreas Gohr 600*639d4c50SAndreas Gohr // endregion 6014d6d17d0SAndreas Gohr} 6024d6d17d0SAndreas Gohr 603