xref: /plugin/approve/action/approve.php (revision c708a86f173ded59efaa534cd399f83e819daa4e)
1<?php
2
3if(!defined('DOKU_INC')) die();
4
5class action_plugin_approve_approve extends DokuWiki_Action_Plugin {
6    /**
7     * @param Doku_Event_Handler $controller
8     */
9    public function register(Doku_Event_Handler $controller) {
10        $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'handle_diff_accept');
11        $controller->register_hook('HTML_SHOWREV_OUTPUT', 'BEFORE', $this, 'handle_showrev');
12        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_approve');
13        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_mark_ready_for_approval');
14        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_viewer');
15        $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_display_banner');
16        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after');
17    }
18
19    /**
20     * @param Doku_Event $event
21     */
22    public function handle_diff_accept(Doku_Event $event) {
23        global $INFO;
24
25        try {
26            /** @var \helper_plugin_approve_db $db_helper */
27            $db_helper = plugin_load('helper', 'approve_db');
28            $sqlite = $db_helper->getDB();
29        } catch (Exception $e) {
30            msg($e->getMessage(), -1);
31            return;
32        }
33        /** @var helper_plugin_approve $helper */
34        $helper = plugin_load('helper', 'approve');
35
36        if (!$helper->use_approve_here($sqlite, $INFO['id'])) return;
37
38        if ($event->data == 'diff' && isset($_GET['approve'])) {
39            $href = wl($INFO['id'], ['approve' => 'approve']);
40            ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>');
41        }
42
43        if ($this->getConf('ready_for_approval') && $event->data == 'diff' && isset($_GET['ready_for_approval'])) {
44            $href = wl($INFO['id'], ['ready_for_approval' => 'ready_for_approval']);
45            ptln('<a href="' . $href . '">'.$this->getLang('approve_ready').'</a>');
46        }
47    }
48
49    /**
50     * @param Doku_Event $event
51     */
52    public function handle_showrev(Doku_Event $event) {
53        global $INFO;
54
55        try {
56            /** @var \helper_plugin_approve_db $db_helper */
57            $db_helper = plugin_load('helper', 'approve_db');
58            $sqlite = $db_helper->getDB();
59        } catch (Exception $e) {
60            msg($e->getMessage(), -1);
61            return;
62        }
63        /** @var helper_plugin_approve $helper */
64        $helper = plugin_load('helper', 'approve');
65
66        if (!$helper->use_approve_here($sqlite, $INFO['id'])) return;
67
68        $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']);
69        if ($last_approved_rev == $INFO['rev']) {
70            $event->preventDefault();
71        }
72    }
73
74    /**
75     * @param Doku_Event $event
76     */
77    public function handle_approve(Doku_Event $event) {
78        global $INFO;
79
80        try {
81            /** @var \helper_plugin_approve_db $db_helper */
82            $db_helper = plugin_load('helper', 'approve_db');
83            $sqlite = $db_helper->getDB();
84        } catch (Exception $e) {
85            msg($e->getMessage(), -1);
86            return;
87        }
88        /** @var helper_plugin_approve $helper */
89        $helper = plugin_load('helper', 'approve');
90
91        if ($event->data != 'show') return;
92        if (!isset($_GET['approve'])) return;
93        if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return;
94        if (!$helper->client_can_approve($INFO['id'], $approver)) return;
95
96        $res = $sqlite->query('SELECT MAX(version)+1 FROM revision
97                                        WHERE page=?', $INFO['id']);
98        $next_version = $sqlite->res2single($res);
99        if (!$next_version) {
100            $next_version = 1;
101        }
102        //approved IS NULL prevents from overriding already approved page
103        $sqlite->query('UPDATE revision
104                        SET approved=?, approved_by=?, version=?
105                        WHERE page=? AND current=1 AND approved IS NULL',
106                        date('c'), $INFO['client'], $next_version, $INFO['id']);
107
108        header('Location: ' . wl($INFO['id']));
109    }
110
111    /**
112     * @param Doku_Event $event
113     */
114    public function handle_mark_ready_for_approval(Doku_Event $event) {
115        global $INFO;
116
117        try {
118            /** @var \helper_plugin_approve_db $db_helper */
119            $db_helper = plugin_load('helper', 'approve_db');
120            $sqlite = $db_helper->getDB();
121        } catch (Exception $e) {
122            msg($e->getMessage(), -1);
123            return;
124        }
125        /** @var helper_plugin_approve $helper */
126        $helper = plugin_load('helper', 'approve');
127
128        if ($event->data != 'show') return;
129        if (!isset($_GET['ready_for_approval'])) return;
130        if (!$helper->use_approve_here($sqlite, $INFO['id'])) return;
131        if (!$helper->client_can_mark_ready_for_approval($INFO['id'])) return;
132
133        $sqlite->query('UPDATE revision SET ready_for_approval=?, ready_for_approval_by=?
134                                WHERE page=? AND current=1 AND ready_for_approval IS NULL',
135        date('c'), $INFO['client'], $INFO['id']);
136
137        header('Location: ' . wl($INFO['id']));
138    }
139
140    /**
141     * Redirect to newest approved page for user that don't have EDIT permission.
142     *
143     * @param Doku_Event $event
144     */
145    public function handle_viewer(Doku_Event $event) {
146        global $INFO;
147
148        try {
149            /** @var \helper_plugin_approve_db $db_helper */
150            $db_helper = plugin_load('helper', 'approve_db');
151            $sqlite = $db_helper->getDB();
152        } catch (Exception $e) {
153            msg($e->getMessage(), -1);
154            return;
155        }
156        /** @var helper_plugin_approve $helper */
157        $helper = plugin_load('helper', 'approve');
158
159        if ($event->data != 'show') return;
160        //apply only to current page
161        if ($INFO['rev'] != 0) return;
162        if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return;
163        if ($helper->client_can_see_drafts($INFO['id'], $approver)) return;
164
165        $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']);
166        //no page is approved
167        if (!$last_approved_rev) return;
168
169        $last_change_date = @filemtime(wikiFN($INFO['id']));
170        //current page is approved
171        if ($last_approved_rev == $last_change_date) return;
172
173        header("Location: " . wl($INFO['id'], ['rev' => $last_approved_rev], false, '&'));
174    }
175
176    /**
177     * @param Doku_Event $event
178     */
179    public function handle_display_banner(Doku_Event $event) {
180        global $INFO, $ID;
181
182        /* Return true if banner should not be displayed for users with or below read only permission. */
183        if (auth_quickaclcheck($ID) <= AUTH_READ && !$this->getConf('display_banner_for_readonly')) {
184            return true;
185        }
186
187        /* Not returned - rendering the banner */
188        try {
189            /** @var \helper_plugin_approve_db $db_helper */
190            $db_helper = plugin_load('helper', 'approve_db');
191            $sqlite = $db_helper->getDB();
192        } catch (Exception $e) {
193            msg($e->getMessage(), -1);
194            return;
195        }
196        /** @var helper_plugin_approve $helper */
197        $helper = plugin_load('helper', 'approve');
198
199        if ($event->data != 'show') return;
200        if (!$INFO['exists']) return;
201        if (!$helper->use_approve_here($sqlite, $INFO['id'], $approver)) return;
202
203//        $last_change_date = p_get_metadata($INFO['id'], 'last_change date');
204        $last_change_date = @filemtime(wikiFN($INFO['id']));
205        $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev'];
206
207
208        $res = $sqlite->query('SELECT ready_for_approval, ready_for_approval_by,
209                                        approved, approved_by, version
210                                FROM revision
211                                WHERE page=? AND rev=?', $INFO['id'], $rev);
212
213        $approve = $sqlite->res_fetch_assoc($res);
214
215        $classes = [];
216        if ($this->getConf('prettyprint')) {
217            $classes[] = 'plugin__approve_noprint';
218        }
219
220        if ($approve['approved']) {
221            $classes[] = 'plugin__approve_green';
222        } elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
223            $classes[] = 'plugin__approve_ready';
224        } else {
225            $classes[] = 'plugin__approve_red';
226        }
227
228        ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">');
229
230//		tpl_pageinfo();
231//		ptln(' | ');
232
233        if ($approve['approved']) {
234            ptln('<strong>'.$this->getLang('approved').'</strong>');
235            ptln(' ' . dformat(strtotime($approve['approved'])));
236
237            if($this->getConf('banner_long')) {
238                ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['approved_by'], true));
239                ptln(' (' . $this->getLang('version') .  ': ' . $approve['version'] . ')');
240            }
241
242            //not the newest page
243            if ($rev != $last_change_date) {
244                $res = $sqlite->query('SELECT rev, current FROM revision
245                                WHERE page=? AND approved IS NOT NULL
246                                ORDER BY rev DESC LIMIT 1', $INFO['id']);
247
248                $last_approve = $sqlite->res_fetch_assoc($res);
249
250                //we can see drafts
251                if ($helper->client_can_see_drafts($INFO['id'], $approver)) {
252                    ptln('<a href="' . wl($INFO['id']) . '">');
253                    ptln($this->getLang($last_approve['current'] ? 'newest_approved' : 'newest_draft'));
254                    ptln('</a>');
255                //we cannot see link to draft but there is some newer approved version
256                } elseif ($last_approve['rev'] != $rev) {
257                    $urlParameters = [];
258                    if (!$last_approve['current']) {
259                        $urlParameters['rev'] = $last_approve['rev'];
260                    }
261                    ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
262                    ptln($this->getLang('newest_approved'));
263                    ptln('</a>');
264                }
265            }
266
267        } else {
268            if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
269                ptln('<strong>'.$this->getLang('marked_approve_ready').'</strong>');
270                ptln(' ' . dformat(strtotime($approve['ready_for_approval'])));
271                ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['ready_for_approval_by'], true));
272            } else {
273                ptln('<strong>'.$this->getLang('draft').'</strong>');
274            }
275
276
277            $res = $sqlite->query('SELECT rev, current FROM revision
278                            WHERE page=? AND approved IS NOT NULL
279                            ORDER BY rev DESC LIMIT 1', $INFO['id']);
280
281            $last_approve = $sqlite->res_fetch_assoc($res);
282
283
284            //not exists approve for current page
285            if (!$last_approve) {
286                //not the newest page
287                if ($rev != $last_change_date) {
288                    ptln('<a href="'.wl($INFO['id']).'">');
289                    ptln($this->getLang('newest_draft'));
290                    ptln('</a>');
291                }
292            } else {
293                $urlParameters = [];
294                if (!$last_approve['current']) {
295                    $urlParameters['rev'] = $last_approve['rev'];
296                }
297                ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
298                ptln($this->getLang('newest_approved'));
299                ptln('</a>');
300            }
301
302            //we are in current page
303            if ($rev == $last_change_date) {
304
305                //compare with the last approved page or 0 if there is no approved versions
306                $last_approved_rev = 0;
307                if (isset($last_approve['rev'])) {
308                    $last_approved_rev = $last_approve['rev'];
309                }
310
311                if ($this->getConf('ready_for_approval') &&
312                    $helper->client_can_mark_ready_for_approval($INFO['id']) &&
313                    !$approve['ready_for_approval']) {
314
315                    $urlParameters = [
316                        'rev' => $last_approved_rev,
317                        'do' => 'diff',
318                        'ready_for_approval' => 'ready_for_approval'
319                    ];
320                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
321                    ptln($this->getLang('approve_ready'));
322                    ptln('</a>');
323                }
324
325                if ($helper->client_can_approve($INFO['id'], $approver)) {
326
327                    $urlParameters = [
328                        'rev' => $last_approved_rev,
329                        'do' => 'diff',
330                        'approve' => 'approve'
331                    ];
332                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
333                    ptln($this->getLang('approve'));
334                    ptln('</a>');
335                }
336            }
337        }
338
339        if ($approver && $this->getConf('banner_long')) {
340            ptln(' | ' . $this->getLang('approver') . ': ' . userlink($approver, true));
341        }
342
343        ptln('</div>');
344    }
345
346    /**
347     * @return bool|string|void
348     */
349    protected function lastRevisionHasntApprovalData($id) {
350
351        try {
352            /** @var \helper_plugin_approve_db $db_helper */
353            $db_helper = plugin_load('helper', 'approve_db');
354            $sqlite = $db_helper->getDB();
355        } catch (Exception $e) {
356            msg($e->getMessage(), -1);
357            return;
358        }
359
360        $res = $sqlite->query('SELECT rev FROM revision
361                                        WHERE page=?
362                                          AND current=1
363                                          AND approved IS NULL
364                                          AND ready_for_approval IS NULL', $id);
365
366        return $sqlite->res2single($res);
367    }
368
369    /**
370     *
371     * @param Doku_Event $event  event object by reference
372     * @return void
373     */
374    public function handle_pagesave_after(Doku_Event $event) {
375        try {
376            /** @var \helper_plugin_approve_db $db_helper */
377            $db_helper = plugin_load('helper', 'approve_db');
378            $sqlite = $db_helper->getDB();
379        } catch (Exception $e) {
380            msg($e->getMessage(), -1);
381            return;
382        }
383        /** @var helper_plugin_approve $helper */
384        $helper = plugin_load('helper', 'approve');
385
386        //no content was changed
387        if (!$event->data['contentChanged']) return;
388
389        $changeType = $event->data['changeType'];
390        if ($changeType == DOKU_CHANGE_TYPE_REVERT) {
391            if ($event->data['oldContent'] == '') {
392                $changeType = DOKU_CHANGE_TYPE_CREATE;
393            } else {
394                $changeType = DOKU_CHANGE_TYPE_EDIT;
395            }
396        }
397
398        $id = $event->data['id'];
399        switch ($changeType) {
400            case DOKU_CHANGE_TYPE_EDIT:
401            case DOKU_CHANGE_TYPE_REVERT:
402            case DOKU_CHANGE_TYPE_MINOR_EDIT:
403                $last_change_date = $event->data['newRevision'];
404
405                //if the current page has approved or ready_for_approval -- keep it
406                $rev = $this->lastRevisionHasntApprovalData($id);
407                if ($rev) {
408                    $sqlite->query('UPDATE revision SET rev=? WHERE page=? AND rev=?',
409                        $last_change_date, $id, $rev);
410
411                } else {
412                    //keep previous record
413                    $sqlite->query('UPDATE revision SET current=0
414                                            WHERE page=?
415                                            AND current=1', $id);
416
417                    $sqlite->storeEntry('revision', [
418                        'page' => $id,
419                        'rev' => $last_change_date,
420                        'current' => 1
421                    ]);
422                }
423                break;
424            case DOKU_CHANGE_TYPE_DELETE:
425                //delete information about availability of a page but keep the history
426                $sqlite->query('DELETE FROM page WHERE page=?', $id);
427
428                //delete revision if no information about approvals
429                $rev = $this->lastRevisionHasntApprovalData($id);
430                if ($rev) {
431                    $sqlite->query('DELETE FROM revision WHERE page=? AND rev=?', $id, $rev);
432                } else {
433                    $sqlite->query('UPDATE revision SET current=0 WHERE page=? AND current=1', $id);
434                }
435
436                break;
437            case DOKU_CHANGE_TYPE_CREATE:
438                if ($helper->isPageAssigned($sqlite, $id, $newApprover)) {
439                    $data = [
440                        'page' => $id,
441                        'hidden' => $helper->in_hidden_namespace($sqlite, $id) ? '1' : '0'
442                    ];
443                    if (!blank($newApprover)) {
444                        $data['approver'] = $newApprover;
445                    }
446                    $sqlite->storeEntry('page', $data);
447                }
448
449                //store revision
450                $last_change_date = $event->data['newRevision'];
451                $sqlite->storeEntry('revision', [
452                    'page' => $id,
453                    'rev' => $last_change_date,
454                    'current' => 1
455                ]);
456                break;
457        }
458    }
459}
460