xref: /plugin/acknowledge/helper.php (revision f09444ff1e83ecee2d791c4f9e5f30833ccbf588)
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
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        }
25*f09444ffSAndreas Gohr        $sqlite->getAdapter()->setUseNativeAlter(true);
26cabb51d3SAndreas Gohr        if (!$sqlite->init('acknowledgement', __DIR__ . '/db')) {
27cabb51d3SAndreas Gohr            return null;
28cabb51d3SAndreas Gohr        }
29cabb51d3SAndreas Gohr
309c3eae1eSAnna Dabrowska        $this->registerUDF($sqlite);
319c3eae1eSAnna Dabrowska
32cabb51d3SAndreas Gohr        return $sqlite;
33cabb51d3SAndreas Gohr    }
34cabb51d3SAndreas Gohr
35cabb51d3SAndreas Gohr    /**
369c3eae1eSAnna Dabrowska     * Register user defined functions
379c3eae1eSAnna Dabrowska     *
389c3eae1eSAnna Dabrowska     * @param helper_plugin_sqlite $sqlite
399c3eae1eSAnna Dabrowska     */
409c3eae1eSAnna Dabrowska    protected function registerUDF($sqlite)
419c3eae1eSAnna Dabrowska    {
429c3eae1eSAnna Dabrowska        $sqlite->create_function('AUTH_ISMEMBER', [$this, 'auth_isMember'], -1);
43*f09444ffSAndreas Gohr        $sqlite->create_function('MATCHES_PAGE_PATTERN', [$this, 'matchPagePattern'], 2);
449c3eae1eSAnna Dabrowska    }
459c3eae1eSAnna Dabrowska
469c3eae1eSAnna Dabrowska    /**
479c3eae1eSAnna Dabrowska     * Wrapper function for auth_isMember which accepts groups as string
489c3eae1eSAnna Dabrowska     *
499c3eae1eSAnna Dabrowska     * @param string $memberList
509c3eae1eSAnna Dabrowska     * @param string $user
519c3eae1eSAnna Dabrowska     * @param string $groups
529c3eae1eSAnna Dabrowska     * @return bool
539c3eae1eSAnna Dabrowska     */
549c3eae1eSAnna Dabrowska    public function auth_isMember($memberList, $user, $groups)
559c3eae1eSAnna Dabrowska    {
5695113ed8SAnna Dabrowska        return auth_isMember($memberList, $user, explode('///', $groups));
579c3eae1eSAnna Dabrowska    }
589c3eae1eSAnna Dabrowska
599c3eae1eSAnna Dabrowska    /**
60ef3ab392SAndreas Gohr     * Delete a page
61ef3ab392SAndreas Gohr     *
62ef3ab392SAndreas Gohr     * Cascades to delete all assigned data, etc.
63ef3ab392SAndreas Gohr     *
64ef3ab392SAndreas Gohr     * @param string $page Page ID
65ef3ab392SAndreas Gohr     */
66ef3ab392SAndreas Gohr    public function removePage($page)
67ef3ab392SAndreas Gohr    {
68ef3ab392SAndreas Gohr        $sqlite = $this->getDB();
69ef3ab392SAndreas Gohr        if (!$sqlite) return;
70ef3ab392SAndreas Gohr
71ef3ab392SAndreas Gohr        $sql = "DELETE FROM pages WHERE page = ?";
72ef3ab392SAndreas Gohr        $sqlite->query($sql, $page);
73ef3ab392SAndreas Gohr    }
74ef3ab392SAndreas Gohr
75ef3ab392SAndreas Gohr    /**
765dee13f7SAnna Dabrowska     * Update last modified date of page if content has changed
77ef3ab392SAndreas Gohr     *
78ef3ab392SAndreas Gohr     * @param string $page Page ID
79ef3ab392SAndreas Gohr     * @param int $lastmod timestamp of last non-minor change
80ef3ab392SAndreas Gohr     */
815dee13f7SAnna Dabrowska    public function storePageDate($page, $lastmod, $newContent)
82ef3ab392SAndreas Gohr    {
83ed4e8871SAnna Dabrowska        $changelog = new \dokuwiki\ChangeLog\PageChangeLog($page);
84789aa26fSAnna Dabrowska        $revs = $changelog->getRevisions(0, 1);
85ed4e8871SAnna Dabrowska
86ed4e8871SAnna Dabrowska        // compare content
87ed4e8871SAnna Dabrowska        $oldContent = str_replace(NL, '', io_readFile(wikiFN($page, $revs[0])));
88ed4e8871SAnna Dabrowska        $newContent = str_replace(NL, '', $newContent);
89ed4e8871SAnna Dabrowska        if ($oldContent === $newContent) return;
90ed4e8871SAnna Dabrowska
91ef3ab392SAndreas Gohr        $sqlite = $this->getDB();
92ef3ab392SAndreas Gohr        if (!$sqlite) return;
93ef3ab392SAndreas Gohr
94ef3ab392SAndreas Gohr        $sql = "REPLACE INTO pages (page, lastmod) VALUES (?,?)";
95ef3ab392SAndreas Gohr        $sqlite->query($sql, $page, $lastmod);
96ef3ab392SAndreas Gohr    }
97ef3ab392SAndreas Gohr
98ef3ab392SAndreas Gohr    /**
99*f09444ffSAndreas Gohr     * Clears direct assignments for a page
100*f09444ffSAndreas Gohr     *
101cabb51d3SAndreas Gohr     * @param string $page Page ID
102cabb51d3SAndreas Gohr     */
103*f09444ffSAndreas Gohr    public function clearPageAssignments($page)
104cabb51d3SAndreas Gohr    {
105cabb51d3SAndreas Gohr        $sqlite = $this->getDB();
106cabb51d3SAndreas Gohr        if (!$sqlite) return;
107cabb51d3SAndreas Gohr
108*f09444ffSAndreas Gohr        $sql = "UPDATE assignments SET pageassignees = '' WHERE page = ?";
109*f09444ffSAndreas Gohr        $sqlite->query($sql, $page);
110*f09444ffSAndreas Gohr    }
111*f09444ffSAndreas Gohr
112*f09444ffSAndreas Gohr    /**
113*f09444ffSAndreas Gohr     * Get all the assignment patterns
114*f09444ffSAndreas Gohr     * @return array (pattern => assignees)
115*f09444ffSAndreas Gohr     */
116*f09444ffSAndreas Gohr    public function getAssignmentPatterns()
117*f09444ffSAndreas Gohr    {
118*f09444ffSAndreas Gohr        $sqlite = $this->getDB();
119*f09444ffSAndreas Gohr        if (!$sqlite) return [];
120*f09444ffSAndreas Gohr
121*f09444ffSAndreas Gohr        $sql = "SELECT pattern, assignees FROM assignments_patterns";
122*f09444ffSAndreas Gohr        $result = $sqlite->query($sql);
123*f09444ffSAndreas Gohr        $patterns = $sqlite->res2arr($result);
124*f09444ffSAndreas Gohr        $sqlite->res_close($result);
125*f09444ffSAndreas Gohr
126*f09444ffSAndreas Gohr        return array_combine(
127*f09444ffSAndreas Gohr            array_column($patterns, 'pattern'),
128*f09444ffSAndreas Gohr            array_column($patterns, 'assignees')
129*f09444ffSAndreas Gohr        );
130*f09444ffSAndreas Gohr    }
131*f09444ffSAndreas Gohr
132*f09444ffSAndreas Gohr    /**
133*f09444ffSAndreas Gohr     * Save new assignment patterns
134*f09444ffSAndreas Gohr     *
135*f09444ffSAndreas Gohr     * This resaves all patterns and reapplies them
136*f09444ffSAndreas Gohr     *
137*f09444ffSAndreas Gohr     * @param array $patterns (pattern => assignees)
138*f09444ffSAndreas Gohr     */
139*f09444ffSAndreas Gohr    public function saveAssignmentPatterns($patterns) {
140*f09444ffSAndreas Gohr        $sqlite = $this->getDB();
141*f09444ffSAndreas Gohr        if (!$sqlite) return;
142*f09444ffSAndreas Gohr
143*f09444ffSAndreas Gohr        $sqlite->query('BEGIN TRANSACTION');
144*f09444ffSAndreas Gohr
145*f09444ffSAndreas Gohr        /** @noinsp0ection SqlWithoutWhere Remove all assignments */
146*f09444ffSAndreas Gohr        $sql = "UPDATE assignments SET autoassignees = ''";
147*f09444ffSAndreas Gohr        $sqlite->query($sql);
148*f09444ffSAndreas Gohr
149*f09444ffSAndreas Gohr        /** @noinspection SqlWithoutWhere Remove all patterns */
150*f09444ffSAndreas Gohr        $sql = "DELETE FROM assignments_patterns";
151*f09444ffSAndreas Gohr        $sqlite->query($sql);
152*f09444ffSAndreas Gohr
153*f09444ffSAndreas Gohr        // insert new patterns and gather affected pages
154*f09444ffSAndreas Gohr        $pages = [];
155*f09444ffSAndreas Gohr
156*f09444ffSAndreas Gohr        $sql = "REPLACE INTO assignments_patterns (pattern, assignees) VALUES (?,?)";
157*f09444ffSAndreas Gohr        foreach ($patterns as $pattern => $assignees) {
158*f09444ffSAndreas Gohr            $pattern = trim($pattern);
159*f09444ffSAndreas Gohr            $assignees = trim($assignees);
160*f09444ffSAndreas Gohr            if (!$pattern || !$assignees) continue;
161*f09444ffSAndreas Gohr            $sqlite->query($sql, $pattern, $assignees);
162*f09444ffSAndreas Gohr
163*f09444ffSAndreas Gohr            // patterns may overlap, so we need to gather all affected pages first
164*f09444ffSAndreas Gohr            $affectedPages = $this->getPagesMatchingPattern($pattern);
165*f09444ffSAndreas Gohr            foreach ($affectedPages as $page) {
166*f09444ffSAndreas Gohr                if(isset($pages[$page])) {
167*f09444ffSAndreas Gohr                    $pages[$page] .= ',' . $assignees;
168*f09444ffSAndreas Gohr                } else {
169*f09444ffSAndreas Gohr                    $pages[$page] = $assignees;
170*f09444ffSAndreas Gohr                }
171*f09444ffSAndreas Gohr            }
172*f09444ffSAndreas Gohr        }
173*f09444ffSAndreas Gohr
174*f09444ffSAndreas Gohr        $sql = "INSERT INTO assignments (page, autoassignees) VALUES (?, ?)
175*f09444ffSAndreas Gohr                ON CONFLICT(page)
176*f09444ffSAndreas Gohr                DO UPDATE SET autoassignees = ?";
177*f09444ffSAndreas Gohr        foreach ($pages as $page => $assignees) {
178*f09444ffSAndreas Gohr            // remove duplicates and empty entries
179*f09444ffSAndreas Gohr            $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees)))));
180*f09444ffSAndreas Gohr            $sqlite->query($sql, $page, $assignees, $assignees);
181*f09444ffSAndreas Gohr        }
182*f09444ffSAndreas Gohr
183*f09444ffSAndreas Gohr        $sqlite->query('COMMIT TRANSACTION');
184*f09444ffSAndreas Gohr    }
185*f09444ffSAndreas Gohr
186*f09444ffSAndreas Gohr    /**
187*f09444ffSAndreas Gohr     * Get all known pages that match the given pattern
188*f09444ffSAndreas Gohr     *
189*f09444ffSAndreas Gohr     * @param $pattern
190*f09444ffSAndreas Gohr     * @return string[]
191*f09444ffSAndreas Gohr     */
192*f09444ffSAndreas Gohr    public function getPagesMatchingPattern($pattern) {
193*f09444ffSAndreas Gohr        $sqlite = $this->getDB();
194*f09444ffSAndreas Gohr        if (!$sqlite) return [];
195*f09444ffSAndreas Gohr
196*f09444ffSAndreas Gohr        $sql = "SELECT page FROM pages WHERE MATCHES_PAGE_PATTERN(?, page)";
197*f09444ffSAndreas Gohr        $result = $sqlite->query($sql, $pattern);
198*f09444ffSAndreas Gohr        $pages = $sqlite->res2arr($result);
199*f09444ffSAndreas Gohr        $sqlite->res_close($result);
200*f09444ffSAndreas Gohr
201*f09444ffSAndreas Gohr        return array_column($pages, 'page');
202*f09444ffSAndreas Gohr    }
203*f09444ffSAndreas Gohr
204*f09444ffSAndreas Gohr    /**
205*f09444ffSAndreas Gohr     * Fills the page index with all unknown pages from the fulltext index
206*f09444ffSAndreas Gohr     * @return void
207*f09444ffSAndreas Gohr     */
208*f09444ffSAndreas Gohr    public function updatePageIndex() {
209*f09444ffSAndreas Gohr        $sqlite = $this->getDB();
210*f09444ffSAndreas Gohr        if (!$sqlite) return;
211*f09444ffSAndreas Gohr
212*f09444ffSAndreas Gohr        $pages = idx_getIndex('page','');
213*f09444ffSAndreas Gohr        $sql = "INSERT OR IGNORE INTO pages (page, lastmod) VALUES (?,?)";
214*f09444ffSAndreas Gohr
215*f09444ffSAndreas Gohr        $sqlite->query('BEGIN TRANSACTION');
216*f09444ffSAndreas Gohr        foreach ($pages as $page) {
217*f09444ffSAndreas Gohr            $page = trim($page);
218*f09444ffSAndreas Gohr            $lastmod = @filemtime(wikiFN($page));
219*f09444ffSAndreas Gohr            if($lastmod) {
220*f09444ffSAndreas Gohr                $sqlite->query($sql, $page, $lastmod);
221*f09444ffSAndreas Gohr            }
222*f09444ffSAndreas Gohr        }
223*f09444ffSAndreas Gohr        $sqlite->query('COMMIT TRANSACTION');
224*f09444ffSAndreas Gohr    }
225*f09444ffSAndreas Gohr
226*f09444ffSAndreas Gohr    /**
227*f09444ffSAndreas Gohr     * Set assignees for a given page as manually specified
228*f09444ffSAndreas Gohr     *
229*f09444ffSAndreas Gohr     * @param string $page Page ID
230*f09444ffSAndreas Gohr     * @param string $assignees
231*f09444ffSAndreas Gohr     * @return void
232*f09444ffSAndreas Gohr     */
233*f09444ffSAndreas Gohr    public function setPageAssignees($page, $assignees) {
234*f09444ffSAndreas Gohr        $sqlite = $this->getDB();
235*f09444ffSAndreas Gohr        if (!$sqlite) return;
236*f09444ffSAndreas Gohr
237*f09444ffSAndreas Gohr        $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees)))));
238*f09444ffSAndreas Gohr
239*f09444ffSAndreas Gohr        $sql = "REPLACE INTO assignments ('page', 'pageassignees') VALUES (?,?)";
240cabb51d3SAndreas Gohr        $sqlite->query($sql, $page, $assignees);
241cabb51d3SAndreas Gohr    }
242cabb51d3SAndreas Gohr
243ef3ab392SAndreas Gohr    /**
244*f09444ffSAndreas Gohr     * Set assignees for a given page from the patterns
245*f09444ffSAndreas Gohr
246ef3ab392SAndreas Gohr     * @param string $page Page ID
247ef3ab392SAndreas Gohr     */
248*f09444ffSAndreas Gohr    public function setAutoAssignees($page)
249ef3ab392SAndreas Gohr    {
250ef3ab392SAndreas Gohr        $sqlite = $this->getDB();
251ef3ab392SAndreas Gohr        if (!$sqlite) return;
252cabb51d3SAndreas Gohr
253*f09444ffSAndreas Gohr        $patterns = $this->getAssignmentPatterns();
254*f09444ffSAndreas Gohr
255*f09444ffSAndreas Gohr        // given assignees
256*f09444ffSAndreas Gohr        $assignees = '';
257*f09444ffSAndreas Gohr
258*f09444ffSAndreas Gohr        // find all patterns that match the page and add the configured assignees
259*f09444ffSAndreas Gohr        foreach ($patterns as $pattern => $assignees) {
260*f09444ffSAndreas Gohr            if ($this->matchPagePattern($pattern, $page)) {
261*f09444ffSAndreas Gohr                $assignees .= ',' . $assignees;
262*f09444ffSAndreas Gohr            }
263*f09444ffSAndreas Gohr        }
264*f09444ffSAndreas Gohr
265*f09444ffSAndreas Gohr        // remove duplicates and empty entries
266*f09444ffSAndreas Gohr        $assignees = join(',', array_unique(array_filter(array_map('trim', explode(',', $assignees)))));
267*f09444ffSAndreas Gohr
268*f09444ffSAndreas Gohr        // store the assignees
269*f09444ffSAndreas Gohr        $sql = "REPLACE INTO assignments ('page', 'autoassignees') VALUES (?,?)";
270*f09444ffSAndreas Gohr        $sqlite->query($sql, $page, $assignees);
271ef3ab392SAndreas Gohr    }
272ef3ab392SAndreas Gohr
273ef3ab392SAndreas Gohr    /**
274ef3ab392SAndreas Gohr     * Is the given user one of the assignees for this page
275ef3ab392SAndreas Gohr     *
276ef3ab392SAndreas Gohr     * @param string $page Page ID
277ef3ab392SAndreas Gohr     * @param string $user user name to check
278ef3ab392SAndreas Gohr     * @param string[] $groups groups this user is in
279ef3ab392SAndreas Gohr     * @return bool
280ef3ab392SAndreas Gohr     */
281ef3ab392SAndreas Gohr    public function isUserAssigned($page, $user, $groups)
282ef3ab392SAndreas Gohr    {
283ef3ab392SAndreas Gohr        $sqlite = $this->getDB();
284ef3ab392SAndreas Gohr        if (!$sqlite) return false;
285ef3ab392SAndreas Gohr
286*f09444ffSAndreas Gohr        $sql = "SELECT pageassignees,autoassignees FROM assignments WHERE page = ?";
287ef3ab392SAndreas Gohr        $result = $sqlite->query($sql, $page);
288*f09444ffSAndreas Gohr        $row = (string)$sqlite->res2row($result);
289ef3ab392SAndreas Gohr        $sqlite->res_close($result);
290*f09444ffSAndreas Gohr        $assignees = $row['pageassignees'] . ',' . $row['autoassignees'];
291ef3ab392SAndreas Gohr        return auth_isMember($assignees, $user, $groups);
292ef3ab392SAndreas Gohr    }
293ef3ab392SAndreas Gohr
294ef3ab392SAndreas Gohr    /**
295ef3ab392SAndreas Gohr     * Has the given user acknowledged the given page?
296ef3ab392SAndreas Gohr     *
297ef3ab392SAndreas Gohr     * @param string $page
298ef3ab392SAndreas Gohr     * @param string $user
2995773dd37SAnna Dabrowska     * @return bool|int timestamp of acknowledgement or false
300ef3ab392SAndreas Gohr     */
301ef3ab392SAndreas Gohr    public function hasUserAcknowledged($page, $user)
302ef3ab392SAndreas Gohr    {
303ef3ab392SAndreas Gohr        $sqlite = $this->getDB();
304ef3ab392SAndreas Gohr        if (!$sqlite) return false;
305ef3ab392SAndreas Gohr
306ef3ab392SAndreas Gohr        $sql = "SELECT ack
307ef3ab392SAndreas Gohr                  FROM acks A, pages B
308ef3ab392SAndreas Gohr                 WHERE A.page = B.page
3095773dd37SAnna Dabrowska                   AND A.page = ?
3105773dd37SAnna Dabrowska                   AND A.user = ?
311ef3ab392SAndreas Gohr                   AND A.ack >= B.lastmod";
312ef3ab392SAndreas Gohr
313ef3ab392SAndreas Gohr        $result = $sqlite->query($sql, $page, $user);
314ef3ab392SAndreas Gohr        $acktime = $sqlite->res2single($result);
315ef3ab392SAndreas Gohr        $sqlite->res_close($result);
316ef3ab392SAndreas Gohr
317ef3ab392SAndreas Gohr        return $acktime ? (int)$acktime : false;
318ef3ab392SAndreas Gohr    }
3195773dd37SAnna Dabrowska
3205773dd37SAnna Dabrowska    /**
321d9a8334dSAnna Dabrowska     * Timestamp of the latest acknowledgment of the given page
322d9a8334dSAnna Dabrowska     * by the given user
323d9a8334dSAnna Dabrowska     *
324d9a8334dSAnna Dabrowska     * @param string $page
325d9a8334dSAnna Dabrowska     * @param string $user
326d9a8334dSAnna Dabrowska     * @return bool|string
327d9a8334dSAnna Dabrowska     */
328d9a8334dSAnna Dabrowska    public function getLatestUserAcknowledgement($page, $user)
329d9a8334dSAnna Dabrowska    {
330d9a8334dSAnna Dabrowska        $sqlite = $this->getDB();
331d9a8334dSAnna Dabrowska        if (!$sqlite) return false;
332d9a8334dSAnna Dabrowska
333d9a8334dSAnna Dabrowska        $sql = "SELECT MAX(ack)
334d9a8334dSAnna Dabrowska                  FROM acks
335d9a8334dSAnna Dabrowska                 WHERE page = ?
336d9a8334dSAnna Dabrowska                   AND user = ?";
337d9a8334dSAnna Dabrowska
338d9a8334dSAnna Dabrowska        $result = $sqlite->query($sql, $page, $user);
339d9a8334dSAnna Dabrowska        $latestAck = $sqlite->res2single($result);
340d9a8334dSAnna Dabrowska        $sqlite->res_close($result);
341d9a8334dSAnna Dabrowska
342d9a8334dSAnna Dabrowska        return $latestAck;
343d9a8334dSAnna Dabrowska    }
344d9a8334dSAnna Dabrowska
345d9a8334dSAnna Dabrowska    /**
3465773dd37SAnna Dabrowska     * Save user's acknowledgement for a given page
3475773dd37SAnna Dabrowska     *
3485773dd37SAnna Dabrowska     * @param string $page
3495773dd37SAnna Dabrowska     * @param string $user
3505773dd37SAnna Dabrowska     * @return bool
3515773dd37SAnna Dabrowska     */
3525773dd37SAnna Dabrowska    public function saveAcknowledgement($page, $user)
3535773dd37SAnna Dabrowska    {
3545773dd37SAnna Dabrowska        $sqlite = $this->getDB();
3555773dd37SAnna Dabrowska        if (!$sqlite) return false;
3565773dd37SAnna Dabrowska
3578e55e483SAnna Dabrowska        $sql = "INSERT INTO acks (page, user, ack) VALUES (?,?, strftime('%s','now'))";
3585773dd37SAnna Dabrowska
3595773dd37SAnna Dabrowska        $result = $sqlite->query($sql, $page, $user);
3605773dd37SAnna Dabrowska        $sqlite->res_close($result);
3615773dd37SAnna Dabrowska        return true;
3625773dd37SAnna Dabrowska
3635773dd37SAnna Dabrowska    }
36474126d4bSAnna Dabrowska
36574126d4bSAnna Dabrowska    /**
36660ed3784SAnna Dabrowska     * Fetch all assignments for a given user, with additional page information,
36760ed3784SAnna Dabrowska     * filtering already granted acknowledgements.
36874126d4bSAnna Dabrowska     *
36974126d4bSAnna Dabrowska     * @param string $user
3708c50976eSAnna Dabrowska     * @param array $groups
37174126d4bSAnna Dabrowska     * @return array|bool
37274126d4bSAnna Dabrowska     */
3738c50976eSAnna Dabrowska    public function getUserAssignments($user, $groups)
37474126d4bSAnna Dabrowska    {
37574126d4bSAnna Dabrowska        $sqlite = $this->getDB();
37674126d4bSAnna Dabrowska        if (!$sqlite) return false;
37774126d4bSAnna Dabrowska
378*f09444ffSAndreas Gohr        $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, C.ack FROM assignments A
37974126d4bSAnna Dabrowska                JOIN pages B
38074126d4bSAnna Dabrowska                ON A.page = B.page
38160ed3784SAnna Dabrowska                LEFT JOIN acks C
3822541208bSAnna Dabrowska                ON A.page = C.page AND ( (C.user = ? AND C.ack > B.lastmod) )
383*f09444ffSAndreas Gohr                WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees , ? , ?)
3848e55e483SAnna Dabrowska                AND ack IS NULL";
38574126d4bSAnna Dabrowska
3862541208bSAnna Dabrowska        $result = $sqlite->query($sql, $user, $user, implode('///', $groups));
38774126d4bSAnna Dabrowska        $assignments = $sqlite->res2arr($result);
38874126d4bSAnna Dabrowska        $sqlite->res_close($result);
38974126d4bSAnna Dabrowska
39074126d4bSAnna Dabrowska        return $assignments;
39174126d4bSAnna Dabrowska    }
39274126d4bSAnna Dabrowska
39374126d4bSAnna Dabrowska    /**
394863b6e48SAndreas Gohr     * Get all pages a user needs to acknowledge and the last acknowledge date
395d6011abdSAnna Dabrowska     *
396863b6e48SAndreas Gohr     * @param string $user
397863b6e48SAndreas Gohr     * @param array $groups
398d6011abdSAnna Dabrowska     * @return array|bool
399d6011abdSAnna Dabrowska     */
400863b6e48SAndreas Gohr    public function getUserAcknowledgements($user, $groups)
401d6011abdSAnna Dabrowska    {
402d6011abdSAnna Dabrowska        $sqlite = $this->getDB();
403d6011abdSAnna Dabrowska        if (!$sqlite) return false;
404d6011abdSAnna Dabrowska
405*f09444ffSAndreas Gohr        $sql = "SELECT A.page, A.pageassignees, A.autoassignees, B.lastmod, C.user, MAX(C.ack) AS ack
406863b6e48SAndreas Gohr                  FROM assignments A
407863b6e48SAndreas Gohr                  JOIN pages B
408863b6e48SAndreas Gohr                    ON A.page = B.page
409863b6e48SAndreas Gohr             LEFT JOIN acks C
410863b6e48SAndreas Gohr                    ON A.page = C.page AND C.user = ?
411*f09444ffSAndreas Gohr                 WHERE AUTH_ISMEMBER(A.pageassignees || ',' || A.autoassignees, ? , ?)
412863b6e48SAndreas Gohr            GROUP BY A.page
413863b6e48SAndreas Gohr            ORDER BY A.page
414863b6e48SAndreas Gohr            ";
415863b6e48SAndreas Gohr
416863b6e48SAndreas Gohr        $result = $sqlite->query($sql, $user, $user, implode('///', $groups));
417863b6e48SAndreas Gohr        $assignments = $sqlite->res2arr($result);
418863b6e48SAndreas Gohr        $sqlite->res_close($result);
419863b6e48SAndreas Gohr
420863b6e48SAndreas Gohr        return $assignments;
421863b6e48SAndreas Gohr    }
422863b6e48SAndreas Gohr
423863b6e48SAndreas Gohr    /**
424c6d8c1d9SAndreas Gohr     * Resolve names of users assigned to a given page
425c6d8c1d9SAndreas Gohr     *
426c6d8c1d9SAndreas Gohr     * This can be slow on huge user bases!
427c6d8c1d9SAndreas Gohr     *
428c6d8c1d9SAndreas Gohr     * @param string $page
429c6d8c1d9SAndreas Gohr     * @return array|false
430c6d8c1d9SAndreas Gohr     */
431c6d8c1d9SAndreas Gohr    public function getPageAssignees($page)
432c6d8c1d9SAndreas Gohr    {
433c6d8c1d9SAndreas Gohr        $sqlite = $this->getDB();
434c6d8c1d9SAndreas Gohr        if (!$sqlite) return false;
435c6d8c1d9SAndreas Gohr        /** @var AuthPlugin $auth */
436c6d8c1d9SAndreas Gohr        global $auth;
437c6d8c1d9SAndreas Gohr
438*f09444ffSAndreas Gohr        $sql = "SELECT pageassignees || ',' || autoassignees AS 'assignments'
439c6d8c1d9SAndreas Gohr                  FROM assignments
440c6d8c1d9SAndreas Gohr                 WHERE page = ?";
441c6d8c1d9SAndreas Gohr        $result = $sqlite->query($sql, $page);
442c6d8c1d9SAndreas Gohr        $assignments = $sqlite->res2single($result);
443c6d8c1d9SAndreas Gohr        $sqlite->res_close($result);
444c6d8c1d9SAndreas Gohr
445c6d8c1d9SAndreas Gohr        $users = [];
446c6d8c1d9SAndreas Gohr        foreach (explode(',', $assignments) as $item) {
447c6d8c1d9SAndreas Gohr            $item = trim($item);
448c6d8c1d9SAndreas Gohr            if ($item === '') continue;
449c6d8c1d9SAndreas Gohr            if ($item[0] == '@') {
450c6d8c1d9SAndreas Gohr                $users = array_merge(
451c6d8c1d9SAndreas Gohr                    $users,
452c6d8c1d9SAndreas Gohr                    array_keys($auth->retrieveUsers(0, 0, ['grps' => substr($item, 1)]))
453c6d8c1d9SAndreas Gohr                );
454c6d8c1d9SAndreas Gohr            } else {
455c6d8c1d9SAndreas Gohr                $users[] = $item;
456c6d8c1d9SAndreas Gohr            }
457c6d8c1d9SAndreas Gohr        }
458c6d8c1d9SAndreas Gohr
459c6d8c1d9SAndreas Gohr        return array_unique($users);
460c6d8c1d9SAndreas Gohr    }
461c6d8c1d9SAndreas Gohr
462c6d8c1d9SAndreas Gohr    /**
463c6d8c1d9SAndreas Gohr     * Get ack status for all assigned users of a given page
464c6d8c1d9SAndreas Gohr     *
465c6d8c1d9SAndreas Gohr     * This can be slow!
466c6d8c1d9SAndreas Gohr     *
467c6d8c1d9SAndreas Gohr     * @param string $page
468c6d8c1d9SAndreas Gohr     * @return array|false
469c6d8c1d9SAndreas Gohr     */
470c6d8c1d9SAndreas Gohr    public function getPageAcknowledgements($page)
471c6d8c1d9SAndreas Gohr    {
472c6d8c1d9SAndreas Gohr        $users = $this->getPageAssignees($page);
473c6d8c1d9SAndreas Gohr        if ($users === false) return false;
474c6d8c1d9SAndreas Gohr        $sqlite = $this->getDB();
475c6d8c1d9SAndreas Gohr        if (!$sqlite) return false;
476c6d8c1d9SAndreas Gohr
477c6d8c1d9SAndreas Gohr        $ulist = $sqlite->quote_and_join($users);
478c6d8c1d9SAndreas Gohr        $sql = "SELECT A.page, A.lastmod, B.user, MAX(B.ack) AS ack
479c6d8c1d9SAndreas Gohr                  FROM pages A
480c6d8c1d9SAndreas Gohr             LEFT JOIN acks B
481c6d8c1d9SAndreas Gohr                    ON A.page = B.page
482c6d8c1d9SAndreas Gohr                   AND B.user IN ($ulist)
483c6d8c1d9SAndreas Gohr                WHERE  A.page = ?
484c6d8c1d9SAndreas Gohr              GROUP BY A.page, B.user
485c6d8c1d9SAndreas Gohr                 ";
486c6d8c1d9SAndreas Gohr        $result = $sqlite->query($sql, $page);
487c6d8c1d9SAndreas Gohr        $acknowledgements = $sqlite->res2arr($result);
488c6d8c1d9SAndreas Gohr        $sqlite->res_close($result);
489c6d8c1d9SAndreas Gohr
490c6d8c1d9SAndreas Gohr        // there should be at least one result, unless the page is unknown
491c6d8c1d9SAndreas Gohr        if (!count($acknowledgements)) return false;
492c6d8c1d9SAndreas Gohr
493c6d8c1d9SAndreas Gohr        $baseinfo = [
494c6d8c1d9SAndreas Gohr            'page' => $acknowledgements[0]['page'],
495c6d8c1d9SAndreas Gohr            'lastmod' => $acknowledgements[0]['lastmod'],
496c6d8c1d9SAndreas Gohr            'user' => null,
497c6d8c1d9SAndreas Gohr            'ack' => null,
498c6d8c1d9SAndreas Gohr        ];
499c6d8c1d9SAndreas Gohr
500c6d8c1d9SAndreas Gohr        // fill up the result with all users that never acknowledged the page
501c6d8c1d9SAndreas Gohr        $combined = [];
502c6d8c1d9SAndreas Gohr        foreach ($acknowledgements as $ack) {
503c6d8c1d9SAndreas Gohr            if ($ack['user'] !== null) {
504c6d8c1d9SAndreas Gohr                $combined[$ack['user']] = $ack;
505c6d8c1d9SAndreas Gohr            }
506c6d8c1d9SAndreas Gohr        }
507c6d8c1d9SAndreas Gohr        foreach ($users as $user) {
508c6d8c1d9SAndreas Gohr            if (!isset($combined[$user])) {
509c6d8c1d9SAndreas Gohr                $combined[$user] = array_merge($baseinfo, ['user' => $user]);
510c6d8c1d9SAndreas Gohr            }
511c6d8c1d9SAndreas Gohr        }
512c6d8c1d9SAndreas Gohr
513c6d8c1d9SAndreas Gohr        ksort($combined);
514c6d8c1d9SAndreas Gohr        return array_values($combined);
515c6d8c1d9SAndreas Gohr    }
516c6d8c1d9SAndreas Gohr
517c6d8c1d9SAndreas Gohr    /**
518863b6e48SAndreas Gohr     * Returns all acknowledgements
519863b6e48SAndreas Gohr     *
520863b6e48SAndreas Gohr     * @param int $limit maximum number of results
521863b6e48SAndreas Gohr     * @return array|bool
522863b6e48SAndreas Gohr     */
523863b6e48SAndreas Gohr    public function getAcknowledgements($limit = 100)
524863b6e48SAndreas Gohr    {
525863b6e48SAndreas Gohr        $sqlite = $this->getDB();
526863b6e48SAndreas Gohr        if (!$sqlite) return false;
527863b6e48SAndreas Gohr
528863b6e48SAndreas Gohr        $sql = '
52984db77b6SAndreas Gohr            SELECT A.page, A.user, B.lastmod, max(A.ack) AS ack
53084db77b6SAndreas Gohr              FROM acks A, pages B
53184db77b6SAndreas Gohr             WHERE A.page = B.page
53284db77b6SAndreas Gohr          GROUP BY A.user, A.page
533863b6e48SAndreas Gohr          ORDER BY ack DESC
534863b6e48SAndreas Gohr             LIMIT ?
535863b6e48SAndreas Gohr              ';
536863b6e48SAndreas Gohr        $result = $sqlite->query($sql, $limit);
537d6011abdSAnna Dabrowska        $acknowledgements = $sqlite->res2arr($result);
538d6011abdSAnna Dabrowska        $sqlite->res_close($result);
539d6011abdSAnna Dabrowska
540d6011abdSAnna Dabrowska        return $acknowledgements;
541d6011abdSAnna Dabrowska    }
542*f09444ffSAndreas Gohr
543*f09444ffSAndreas Gohr    /**
544*f09444ffSAndreas Gohr     * Check if the given pattern matches the given page
545*f09444ffSAndreas Gohr     *
546*f09444ffSAndreas Gohr     * @param string $pattern the pattern to check against
547*f09444ffSAndreas Gohr     * @param string $page the cleaned pageid to check
548*f09444ffSAndreas Gohr     * @return bool
549*f09444ffSAndreas Gohr     */
550*f09444ffSAndreas Gohr    public function matchPagePattern($pattern, $page)
551*f09444ffSAndreas Gohr    {
552*f09444ffSAndreas Gohr        if (trim($pattern, ':') == '**') return true; // match all
553*f09444ffSAndreas Gohr
554*f09444ffSAndreas Gohr        // regex patterns
555*f09444ffSAndreas Gohr        if ($pattern[0] == '/') {
556*f09444ffSAndreas Gohr            return (bool)preg_match($pattern, ":$page");
557*f09444ffSAndreas Gohr        }
558*f09444ffSAndreas Gohr
559*f09444ffSAndreas Gohr        $pns = ':' . getNS($page) . ':';
560*f09444ffSAndreas Gohr
561*f09444ffSAndreas Gohr        $ans = ':' . cleanID($pattern) . ':';
562*f09444ffSAndreas Gohr        if (substr($pattern, -2) == '**') {
563*f09444ffSAndreas Gohr            // upper namespaces match
564*f09444ffSAndreas Gohr            if (strpos($pns, $ans) === 0) {
565*f09444ffSAndreas Gohr                return true;
566*f09444ffSAndreas Gohr            }
567*f09444ffSAndreas Gohr        } elseif (substr($pattern, -1) == '*') {
568*f09444ffSAndreas Gohr            // namespaces match exact
569*f09444ffSAndreas Gohr            if ($ans == $pns) {
570*f09444ffSAndreas Gohr                return true;
571*f09444ffSAndreas Gohr            }
572*f09444ffSAndreas Gohr        } else {
573*f09444ffSAndreas Gohr            // exact match
574*f09444ffSAndreas Gohr            if (cleanID($pattern) == $page) {
575*f09444ffSAndreas Gohr                return true;
576*f09444ffSAndreas Gohr            }
577*f09444ffSAndreas Gohr        }
578*f09444ffSAndreas Gohr
579*f09444ffSAndreas Gohr        return false;
580*f09444ffSAndreas Gohr    }
5814d6d17d0SAndreas Gohr}
5824d6d17d0SAndreas Gohr
583