14d6d17d0SAndreas Gohr<?php 2*c6d8c1d9SAndreas Gohr 3*c6d8c1d9SAndreas Gohruse dokuwiki\Extension\AuthPlugin; 4*c6d8c1d9SAndreas 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 14cabb51d3SAndreas Gohr /** 15cabb51d3SAndreas Gohr * @return helper_plugin_sqlite|null 16cabb51d3SAndreas Gohr */ 17cabb51d3SAndreas Gohr public function getDB() 18cabb51d3SAndreas Gohr { 19cabb51d3SAndreas Gohr /** @var \helper_plugin_sqlite $sqlite */ 20cabb51d3SAndreas Gohr $sqlite = plugin_load('helper', 'sqlite'); 21cabb51d3SAndreas Gohr if ($sqlite === null) { 22cabb51d3SAndreas Gohr msg($this->getLang('error sqlite plugin missing'), -1); 23cabb51d3SAndreas Gohr return null; 24cabb51d3SAndreas Gohr } 25cabb51d3SAndreas Gohr if (!$sqlite->init('acknowledgement', __DIR__ . '/db')) { 26cabb51d3SAndreas Gohr return null; 27cabb51d3SAndreas Gohr } 28cabb51d3SAndreas Gohr 299c3eae1eSAnna Dabrowska $this->registerUDF($sqlite); 309c3eae1eSAnna Dabrowska 31cabb51d3SAndreas Gohr return $sqlite; 32cabb51d3SAndreas Gohr } 33cabb51d3SAndreas Gohr 34cabb51d3SAndreas Gohr /** 359c3eae1eSAnna Dabrowska * Register user defined functions 369c3eae1eSAnna Dabrowska * 379c3eae1eSAnna Dabrowska * @param helper_plugin_sqlite $sqlite 389c3eae1eSAnna Dabrowska */ 399c3eae1eSAnna Dabrowska protected function registerUDF($sqlite) 409c3eae1eSAnna Dabrowska { 419c3eae1eSAnna Dabrowska $sqlite->create_function('AUTH_ISMEMBER', [$this, 'auth_isMember'], -1); 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 /** 58ef3ab392SAndreas Gohr * Delete a page 59ef3ab392SAndreas Gohr * 60ef3ab392SAndreas Gohr * Cascades to delete all assigned data, etc. 61ef3ab392SAndreas Gohr * 62ef3ab392SAndreas Gohr * @param string $page Page ID 63ef3ab392SAndreas Gohr */ 64ef3ab392SAndreas Gohr public function removePage($page) 65ef3ab392SAndreas Gohr { 66ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 67ef3ab392SAndreas Gohr if (!$sqlite) return; 68ef3ab392SAndreas Gohr 69ef3ab392SAndreas Gohr $sql = "DELETE FROM pages WHERE page = ?"; 70ef3ab392SAndreas Gohr $sqlite->query($sql, $page); 71ef3ab392SAndreas Gohr } 72ef3ab392SAndreas Gohr 73ef3ab392SAndreas Gohr /** 745dee13f7SAnna Dabrowska * Update last modified date of page if content has changed 75ef3ab392SAndreas Gohr * 76ef3ab392SAndreas Gohr * @param string $page Page ID 77ef3ab392SAndreas Gohr * @param int $lastmod timestamp of last non-minor change 78ef3ab392SAndreas Gohr */ 795dee13f7SAnna Dabrowska public function storePageDate($page, $lastmod, $newContent) 80ef3ab392SAndreas Gohr { 81ed4e8871SAnna Dabrowska $changelog = new \dokuwiki\ChangeLog\PageChangeLog($page); 82789aa26fSAnna Dabrowska $revs = $changelog->getRevisions(0, 1); 83ed4e8871SAnna Dabrowska 84ed4e8871SAnna Dabrowska // compare content 85ed4e8871SAnna Dabrowska $oldContent = str_replace(NL, '', io_readFile(wikiFN($page, $revs[0]))); 86ed4e8871SAnna Dabrowska $newContent = str_replace(NL, '', $newContent); 87ed4e8871SAnna Dabrowska if ($oldContent === $newContent) return; 88ed4e8871SAnna Dabrowska 89ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 90ef3ab392SAndreas Gohr if (!$sqlite) return; 91ef3ab392SAndreas Gohr 92ef3ab392SAndreas Gohr $sql = "REPLACE INTO pages (page, lastmod) VALUES (?,?)"; 93ef3ab392SAndreas Gohr $sqlite->query($sql, $page, $lastmod); 94ef3ab392SAndreas Gohr } 95ef3ab392SAndreas Gohr 96ef3ab392SAndreas Gohr /** 97cabb51d3SAndreas Gohr * @param string $page Page ID 98cabb51d3SAndreas Gohr * @param string $assignees comma separated list of users and groups 99cabb51d3SAndreas Gohr */ 100cabb51d3SAndreas Gohr public function setAssignees($page, $assignees) 101cabb51d3SAndreas Gohr { 102cabb51d3SAndreas Gohr $sqlite = $this->getDB(); 103cabb51d3SAndreas Gohr if (!$sqlite) return; 104cabb51d3SAndreas Gohr 105cabb51d3SAndreas Gohr $sql = "REPLACE INTO assignments ('page', 'assignee') VALUES (?,?)"; 106cabb51d3SAndreas Gohr $sqlite->query($sql, $page, $assignees); 107cabb51d3SAndreas Gohr } 108cabb51d3SAndreas Gohr 109ef3ab392SAndreas Gohr /** 1105773dd37SAnna Dabrowska * Clears assignments for a page 111ef3ab392SAndreas Gohr * 112ef3ab392SAndreas Gohr * @param string $page Page ID 113ef3ab392SAndreas Gohr */ 114ef3ab392SAndreas Gohr public function clearAssignments($page) 115ef3ab392SAndreas Gohr { 116ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 117ef3ab392SAndreas Gohr if (!$sqlite) return; 118cabb51d3SAndreas Gohr 119ef3ab392SAndreas Gohr $sql = "DELETE FROM assignments WHERE page = ?"; 120ef3ab392SAndreas Gohr $sqlite->query($sql, $page); 121ef3ab392SAndreas Gohr } 122ef3ab392SAndreas Gohr 123ef3ab392SAndreas Gohr /** 124ef3ab392SAndreas Gohr * Is the given user one of the assignees for this page 125ef3ab392SAndreas Gohr * 126ef3ab392SAndreas Gohr * @param string $page Page ID 127ef3ab392SAndreas Gohr * @param string $user user name to check 128ef3ab392SAndreas Gohr * @param string[] $groups groups this user is in 129ef3ab392SAndreas Gohr * @return bool 130ef3ab392SAndreas Gohr */ 131ef3ab392SAndreas Gohr public function isUserAssigned($page, $user, $groups) 132ef3ab392SAndreas Gohr { 133ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 134ef3ab392SAndreas Gohr if (!$sqlite) return false; 135ef3ab392SAndreas Gohr 136ef3ab392SAndreas Gohr $sql = "SELECT assignee FROM assignments WHERE page = ?"; 137ef3ab392SAndreas Gohr $result = $sqlite->query($sql, $page); 138ef3ab392SAndreas Gohr $assignees = (string)$sqlite->res2single($result); 139ef3ab392SAndreas Gohr $sqlite->res_close($result); 140ef3ab392SAndreas Gohr 141ef3ab392SAndreas Gohr return auth_isMember($assignees, $user, $groups); 142ef3ab392SAndreas Gohr } 143ef3ab392SAndreas Gohr 144ef3ab392SAndreas Gohr /** 145ef3ab392SAndreas Gohr * Has the given user acknowledged the given page? 146ef3ab392SAndreas Gohr * 147ef3ab392SAndreas Gohr * @param string $page 148ef3ab392SAndreas Gohr * @param string $user 1495773dd37SAnna Dabrowska * @return bool|int timestamp of acknowledgement or false 150ef3ab392SAndreas Gohr */ 151ef3ab392SAndreas Gohr public function hasUserAcknowledged($page, $user) 152ef3ab392SAndreas Gohr { 153ef3ab392SAndreas Gohr $sqlite = $this->getDB(); 154ef3ab392SAndreas Gohr if (!$sqlite) return false; 155ef3ab392SAndreas Gohr 156ef3ab392SAndreas Gohr $sql = "SELECT ack 157ef3ab392SAndreas Gohr FROM acks A, pages B 158ef3ab392SAndreas Gohr WHERE A.page = B.page 1595773dd37SAnna Dabrowska AND A.page = ? 1605773dd37SAnna Dabrowska AND A.user = ? 161ef3ab392SAndreas Gohr AND A.ack >= B.lastmod"; 162ef3ab392SAndreas Gohr 163ef3ab392SAndreas Gohr $result = $sqlite->query($sql, $page, $user); 164ef3ab392SAndreas Gohr $acktime = $sqlite->res2single($result); 165ef3ab392SAndreas Gohr $sqlite->res_close($result); 166ef3ab392SAndreas Gohr 167ef3ab392SAndreas Gohr return $acktime ? (int)$acktime : false; 168ef3ab392SAndreas Gohr } 1695773dd37SAnna Dabrowska 1705773dd37SAnna Dabrowska /** 171d9a8334dSAnna Dabrowska * Timestamp of the latest acknowledgment of the given page 172d9a8334dSAnna Dabrowska * by the given user 173d9a8334dSAnna Dabrowska * 174d9a8334dSAnna Dabrowska * @param string $page 175d9a8334dSAnna Dabrowska * @param string $user 176d9a8334dSAnna Dabrowska * @return bool|string 177d9a8334dSAnna Dabrowska */ 178d9a8334dSAnna Dabrowska public function getLatestUserAcknowledgement($page, $user) 179d9a8334dSAnna Dabrowska { 180d9a8334dSAnna Dabrowska $sqlite = $this->getDB(); 181d9a8334dSAnna Dabrowska if (!$sqlite) return false; 182d9a8334dSAnna Dabrowska 183d9a8334dSAnna Dabrowska $sql = "SELECT MAX(ack) 184d9a8334dSAnna Dabrowska FROM acks 185d9a8334dSAnna Dabrowska WHERE page = ? 186d9a8334dSAnna Dabrowska AND user = ?"; 187d9a8334dSAnna Dabrowska 188d9a8334dSAnna Dabrowska $result = $sqlite->query($sql, $page, $user); 189d9a8334dSAnna Dabrowska $latestAck = $sqlite->res2single($result); 190d9a8334dSAnna Dabrowska $sqlite->res_close($result); 191d9a8334dSAnna Dabrowska 192d9a8334dSAnna Dabrowska return $latestAck; 193d9a8334dSAnna Dabrowska } 194d9a8334dSAnna Dabrowska 195d9a8334dSAnna Dabrowska /** 1965773dd37SAnna Dabrowska * Save user's acknowledgement for a given page 1975773dd37SAnna Dabrowska * 1985773dd37SAnna Dabrowska * @param string $page 1995773dd37SAnna Dabrowska * @param string $user 2005773dd37SAnna Dabrowska * @return bool 2015773dd37SAnna Dabrowska */ 2025773dd37SAnna Dabrowska public function saveAcknowledgement($page, $user) 2035773dd37SAnna Dabrowska { 2045773dd37SAnna Dabrowska $sqlite = $this->getDB(); 2055773dd37SAnna Dabrowska if (!$sqlite) return false; 2065773dd37SAnna Dabrowska 2078e55e483SAnna Dabrowska $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))"; 2085773dd37SAnna Dabrowska 2095773dd37SAnna Dabrowska $result = $sqlite->query($sql, $page, $user); 2105773dd37SAnna Dabrowska $sqlite->res_close($result); 2115773dd37SAnna Dabrowska return true; 2125773dd37SAnna Dabrowska 2135773dd37SAnna Dabrowska } 21474126d4bSAnna Dabrowska 21574126d4bSAnna Dabrowska /** 21660ed3784SAnna Dabrowska * Fetch all assignments for a given user, with additional page information, 21760ed3784SAnna Dabrowska * filtering already granted acknowledgements. 21874126d4bSAnna Dabrowska * 21974126d4bSAnna Dabrowska * @param string $user 2208c50976eSAnna Dabrowska * @param array $groups 22174126d4bSAnna Dabrowska * @return array|bool 22274126d4bSAnna Dabrowska */ 2238c50976eSAnna Dabrowska public function getUserAssignments($user, $groups) 22474126d4bSAnna Dabrowska { 22574126d4bSAnna Dabrowska $sqlite = $this->getDB(); 22674126d4bSAnna Dabrowska if (!$sqlite) return false; 22774126d4bSAnna Dabrowska 22860ed3784SAnna Dabrowska $sql = "SELECT A.page, A.assignee, B.lastmod, C.user, C.ack FROM assignments A 22974126d4bSAnna Dabrowska JOIN pages B 23074126d4bSAnna Dabrowska ON A.page = B.page 23160ed3784SAnna Dabrowska LEFT JOIN acks C 2322541208bSAnna Dabrowska ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) ) 23360ed3784SAnna Dabrowska WHERE AUTH_ISMEMBER(A.assignee, ? , ?) 2348e55e483SAnna Dabrowska AND ack IS NULL"; 23574126d4bSAnna Dabrowska 2362541208bSAnna Dabrowska $result = $sqlite->query($sql, $user, $user, implode('///', $groups)); 23774126d4bSAnna Dabrowska $assignments = $sqlite->res2arr($result); 23874126d4bSAnna Dabrowska $sqlite->res_close($result); 23974126d4bSAnna Dabrowska 24074126d4bSAnna Dabrowska return $assignments; 24174126d4bSAnna Dabrowska } 24274126d4bSAnna Dabrowska 24374126d4bSAnna Dabrowska /** 244863b6e48SAndreas Gohr * Get all pages a user needs to acknowledge and the last acknowledge date 245d6011abdSAnna Dabrowska * 246863b6e48SAndreas Gohr * @param string $user 247863b6e48SAndreas Gohr * @param array $groups 248d6011abdSAnna Dabrowska * @return array|bool 249d6011abdSAnna Dabrowska */ 250863b6e48SAndreas Gohr public function getUserAcknowledgements($user, $groups) 251d6011abdSAnna Dabrowska { 252d6011abdSAnna Dabrowska $sqlite = $this->getDB(); 253d6011abdSAnna Dabrowska if (!$sqlite) return false; 254d6011abdSAnna Dabrowska 255863b6e48SAndreas Gohr $sql = "SELECT A.page, A.assignee, B.lastmod, C.user, MAX(C.ack) AS ack 256863b6e48SAndreas Gohr FROM assignments A 257863b6e48SAndreas Gohr JOIN pages B 258863b6e48SAndreas Gohr ON A.page = B.page 259863b6e48SAndreas Gohr LEFT JOIN acks C 260863b6e48SAndreas Gohr ON A.page = C.page AND C.user = ? 261863b6e48SAndreas Gohr WHERE AUTH_ISMEMBER(A.assignee, ? , ?) 262863b6e48SAndreas Gohr GROUP BY A.page 263863b6e48SAndreas Gohr ORDER BY A.page 264863b6e48SAndreas Gohr "; 265863b6e48SAndreas Gohr 266863b6e48SAndreas Gohr $result = $sqlite->query($sql, $user, $user, implode('///', $groups)); 267863b6e48SAndreas Gohr $assignments = $sqlite->res2arr($result); 268863b6e48SAndreas Gohr $sqlite->res_close($result); 269863b6e48SAndreas Gohr 270863b6e48SAndreas Gohr return $assignments; 271863b6e48SAndreas Gohr } 272863b6e48SAndreas Gohr 273863b6e48SAndreas Gohr /** 274*c6d8c1d9SAndreas Gohr * Resolve names of users assigned to a given page 275*c6d8c1d9SAndreas Gohr * 276*c6d8c1d9SAndreas Gohr * This can be slow on huge user bases! 277*c6d8c1d9SAndreas Gohr * 278*c6d8c1d9SAndreas Gohr * @param string $page 279*c6d8c1d9SAndreas Gohr * @return array|false 280*c6d8c1d9SAndreas Gohr */ 281*c6d8c1d9SAndreas Gohr public function getPageAssignees($page) 282*c6d8c1d9SAndreas Gohr { 283*c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 284*c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 285*c6d8c1d9SAndreas Gohr /** @var AuthPlugin $auth */ 286*c6d8c1d9SAndreas Gohr global $auth; 287*c6d8c1d9SAndreas Gohr 288*c6d8c1d9SAndreas Gohr $sql = "SELECT assignee 289*c6d8c1d9SAndreas Gohr FROM assignments 290*c6d8c1d9SAndreas Gohr WHERE page = ?"; 291*c6d8c1d9SAndreas Gohr $result = $sqlite->query($sql, $page); 292*c6d8c1d9SAndreas Gohr $assignments = $sqlite->res2single($result); 293*c6d8c1d9SAndreas Gohr $sqlite->res_close($result); 294*c6d8c1d9SAndreas Gohr 295*c6d8c1d9SAndreas Gohr $users = []; 296*c6d8c1d9SAndreas Gohr foreach (explode(',', $assignments) as $item) { 297*c6d8c1d9SAndreas Gohr $item = trim($item); 298*c6d8c1d9SAndreas Gohr if ($item === '') continue; 299*c6d8c1d9SAndreas Gohr if ($item[0] == '@') { 300*c6d8c1d9SAndreas Gohr $users = array_merge( 301*c6d8c1d9SAndreas Gohr $users, 302*c6d8c1d9SAndreas Gohr array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)])) 303*c6d8c1d9SAndreas Gohr ); 304*c6d8c1d9SAndreas Gohr } else { 305*c6d8c1d9SAndreas Gohr $users[] = $item; 306*c6d8c1d9SAndreas Gohr } 307*c6d8c1d9SAndreas Gohr } 308*c6d8c1d9SAndreas Gohr 309*c6d8c1d9SAndreas Gohr return array_unique($users); 310*c6d8c1d9SAndreas Gohr } 311*c6d8c1d9SAndreas Gohr 312*c6d8c1d9SAndreas Gohr /** 313*c6d8c1d9SAndreas Gohr * Get ack status for all assigned users of a given page 314*c6d8c1d9SAndreas Gohr * 315*c6d8c1d9SAndreas Gohr * This can be slow! 316*c6d8c1d9SAndreas Gohr * 317*c6d8c1d9SAndreas Gohr * @param string $page 318*c6d8c1d9SAndreas Gohr * @return array|false 319*c6d8c1d9SAndreas Gohr */ 320*c6d8c1d9SAndreas Gohr public function getPageAcknowledgements($page) 321*c6d8c1d9SAndreas Gohr { 322*c6d8c1d9SAndreas Gohr $users = $this->getPageAssignees($page); 323*c6d8c1d9SAndreas Gohr if ($users === false) return false; 324*c6d8c1d9SAndreas Gohr $sqlite = $this->getDB(); 325*c6d8c1d9SAndreas Gohr if (!$sqlite) return false; 326*c6d8c1d9SAndreas Gohr 327*c6d8c1d9SAndreas Gohr $ulist = $sqlite->quote_and_join($users); 328*c6d8c1d9SAndreas Gohr $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack 329*c6d8c1d9SAndreas Gohr FROM pages A 330*c6d8c1d9SAndreas Gohr LEFT JOIN acks B 331*c6d8c1d9SAndreas Gohr ON A.page = B.page 332*c6d8c1d9SAndreas Gohr AND B.user IN ($ulist) 333*c6d8c1d9SAndreas Gohr WHERE A.page = ? 334*c6d8c1d9SAndreas Gohr GROUP BY A.page, B.user 335*c6d8c1d9SAndreas Gohr "; 336*c6d8c1d9SAndreas Gohr $result = $sqlite->query($sql, $page); 337*c6d8c1d9SAndreas Gohr $acknowledgements = $sqlite->res2arr($result); 338*c6d8c1d9SAndreas Gohr $sqlite->res_close($result); 339*c6d8c1d9SAndreas Gohr 340*c6d8c1d9SAndreas Gohr // there should be at least one result, unless the page is unknown 341*c6d8c1d9SAndreas Gohr if (!count($acknowledgements)) return false; 342*c6d8c1d9SAndreas Gohr 343*c6d8c1d9SAndreas Gohr $baseinfo = [ 344*c6d8c1d9SAndreas Gohr 'page' => $acknowledgements[0]['page'], 345*c6d8c1d9SAndreas Gohr 'lastmod' => $acknowledgements[0]['lastmod'], 346*c6d8c1d9SAndreas Gohr 'user' => null, 347*c6d8c1d9SAndreas Gohr 'ack' => null, 348*c6d8c1d9SAndreas Gohr ]; 349*c6d8c1d9SAndreas Gohr 350*c6d8c1d9SAndreas Gohr // fill up the result with all users that never acknowledged the page 351*c6d8c1d9SAndreas Gohr $combined = []; 352*c6d8c1d9SAndreas Gohr foreach ($acknowledgements as $ack) { 353*c6d8c1d9SAndreas Gohr if ($ack['user'] !== null) { 354*c6d8c1d9SAndreas Gohr $combined[$ack['user']] = $ack; 355*c6d8c1d9SAndreas Gohr } 356*c6d8c1d9SAndreas Gohr } 357*c6d8c1d9SAndreas Gohr foreach ($users as $user) { 358*c6d8c1d9SAndreas Gohr if (!isset($combined[$user])) { 359*c6d8c1d9SAndreas Gohr $combined[$user] = array_merge($baseinfo, ['user' => $user]); 360*c6d8c1d9SAndreas Gohr } 361*c6d8c1d9SAndreas Gohr } 362*c6d8c1d9SAndreas Gohr 363*c6d8c1d9SAndreas Gohr ksort($combined); 364*c6d8c1d9SAndreas Gohr return array_values($combined); 365*c6d8c1d9SAndreas Gohr } 366*c6d8c1d9SAndreas Gohr 367*c6d8c1d9SAndreas Gohr /** 368863b6e48SAndreas Gohr * Returns all acknowledgements 369863b6e48SAndreas Gohr * 370863b6e48SAndreas Gohr * @param int $limit maximum number of results 371863b6e48SAndreas Gohr * @return array|bool 372863b6e48SAndreas Gohr */ 373863b6e48SAndreas Gohr public function getAcknowledgements($limit = 100) 374863b6e48SAndreas Gohr { 375863b6e48SAndreas Gohr $sqlite = $this->getDB(); 376863b6e48SAndreas Gohr if (!$sqlite) return false; 377863b6e48SAndreas Gohr 378863b6e48SAndreas Gohr $sql = ' 379863b6e48SAndreas Gohr SELECT page, user, max(ack) AS ack 380863b6e48SAndreas Gohr FROM acks 381863b6e48SAndreas Gohr GROUP BY user,page 382863b6e48SAndreas Gohr ORDER BY ack DESC 383863b6e48SAndreas Gohr LIMIT ? 384863b6e48SAndreas Gohr '; 385863b6e48SAndreas Gohr $result = $sqlite->query($sql, $limit); 386d6011abdSAnna Dabrowska $acknowledgements = $sqlite->res2arr($result); 387d6011abdSAnna Dabrowska $sqlite->res_close($result); 388d6011abdSAnna Dabrowska 389d6011abdSAnna Dabrowska return $acknowledgements; 390d6011abdSAnna Dabrowska } 3914d6d17d0SAndreas Gohr} 3924d6d17d0SAndreas Gohr 393