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        $last_approved_rev = $helper->find_last_approved($sqlite, $INFO['id']);
215
216        $classes = [];
217        if ($this->getConf('prettyprint')) {
218            $classes[] = 'plugin__approve_noprint';
219        }
220
221        if ($approve['approved'] && $rev == $last_approved_rev) {
222            $classes[] = 'plugin__approve_approved';
223        } elseif ($approve['approved']) {
224                $classes[] = 'plugin__approve_old_approved';
225        } elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
226            $classes[] = 'plugin__approve_ready';
227        } else {
228            $classes[] = 'plugin__approve_draft';
229        }
230
231        ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">');
232
233//		tpl_pageinfo();
234//		ptln(' | ');
235
236        if ($approve['approved']) {
237            ptln('<strong>'.$this->getLang('approved').'</strong>');
238            ptln(' ' . dformat(strtotime($approve['approved'])));
239
240            if($this->getConf('banner_long')) {
241                ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['approved_by'], true));
242                ptln(' (' . $this->getLang('version') .  ': ' . $approve['version'] . ')');
243            }
244
245            //not the newest page
246            if ($rev != $last_change_date) {
247                $res = $sqlite->query('SELECT rev, current FROM revision
248                                WHERE page=? AND approved IS NOT NULL
249                                ORDER BY rev DESC LIMIT 1', $INFO['id']);
250
251                $last_approve = $sqlite->res_fetch_assoc($res);
252
253                //we can see drafts
254                if ($helper->client_can_see_drafts($INFO['id'], $approver)) {
255                    ptln('<a href="' . wl($INFO['id']) . '">');
256                    ptln($this->getLang($last_approve['current'] ? 'newest_approved' : 'newest_draft'));
257                    ptln('</a>');
258                //we cannot see link to draft but there is some newer approved version
259                } elseif ($last_approve['rev'] != $rev) {
260                    $urlParameters = [];
261                    if (!$last_approve['current']) {
262                        $urlParameters['rev'] = $last_approve['rev'];
263                    }
264                    ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
265                    ptln($this->getLang('newest_approved'));
266                    ptln('</a>');
267                }
268            }
269
270        } else {
271            if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
272                ptln('<strong>'.$this->getLang('marked_approve_ready').'</strong>');
273                ptln(' ' . dformat(strtotime($approve['ready_for_approval'])));
274                ptln(' ' . $this->getLang('by') . ' ' . userlink($approve['ready_for_approval_by'], true));
275            } else {
276                ptln('<strong>'.$this->getLang('draft').'</strong>');
277            }
278
279
280            $res = $sqlite->query('SELECT rev, current FROM revision
281                            WHERE page=? AND approved IS NOT NULL
282                            ORDER BY rev DESC LIMIT 1', $INFO['id']);
283
284            $last_approve = $sqlite->res_fetch_assoc($res);
285
286
287            //not exists approve for current page
288            if (!$last_approve) {
289                //not the newest page
290                if ($rev != $last_change_date) {
291                    ptln('<a href="'.wl($INFO['id']).'">');
292                    ptln($this->getLang('newest_draft'));
293                    ptln('</a>');
294                }
295            } else {
296                $urlParameters = [];
297                if (!$last_approve['current']) {
298                    $urlParameters['rev'] = $last_approve['rev'];
299                }
300                ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
301                ptln($this->getLang('newest_approved'));
302                ptln('</a>');
303            }
304
305            //we are in current page
306            if ($rev == $last_change_date) {
307
308                //compare with the last approved page or 0 if there is no approved versions
309                $last_approved_rev = 0;
310                if (isset($last_approve['rev'])) {
311                    $last_approved_rev = $last_approve['rev'];
312                }
313
314                if ($this->getConf('ready_for_approval') &&
315                    $helper->client_can_mark_ready_for_approval($INFO['id']) &&
316                    !$approve['ready_for_approval']) {
317
318                    $urlParameters = [
319                        'rev' => $last_approved_rev,
320                        'do' => 'diff',
321                        'ready_for_approval' => 'ready_for_approval'
322                    ];
323                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
324                    ptln($this->getLang('approve_ready'));
325                    ptln('</a>');
326                }
327
328                if ($helper->client_can_approve($INFO['id'], $approver)) {
329
330                    $urlParameters = [
331                        'rev' => $last_approved_rev,
332                        'do' => 'diff',
333                        'approve' => 'approve'
334                    ];
335                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
336                    ptln($this->getLang('approve'));
337                    ptln('</a>');
338                }
339            }
340        }
341
342        if ($approver && $this->getConf('banner_long')) {
343            ptln(' | ' . $this->getLang('approver') . ': ' . userlink($approver, true));
344        }
345
346        ptln('</div>');
347    }
348
349    /**
350     * @return bool|string|void
351     */
352    protected function lastRevisionHasntApprovalData($id) {
353
354        try {
355            /** @var \helper_plugin_approve_db $db_helper */
356            $db_helper = plugin_load('helper', 'approve_db');
357            $sqlite = $db_helper->getDB();
358        } catch (Exception $e) {
359            msg($e->getMessage(), -1);
360            return;
361        }
362
363        $res = $sqlite->query('SELECT rev FROM revision
364                                        WHERE page=?
365                                          AND current=1
366                                          AND approved IS NULL
367                                          AND ready_for_approval IS NULL', $id);
368
369        return $sqlite->res2single($res);
370    }
371
372    /**
373     *
374     * @param Doku_Event $event  event object by reference
375     * @return void
376     */
377    public function handle_pagesave_after(Doku_Event $event) {
378        try {
379            /** @var \helper_plugin_approve_db $db_helper */
380            $db_helper = plugin_load('helper', 'approve_db');
381            $sqlite = $db_helper->getDB();
382        } catch (Exception $e) {
383            msg($e->getMessage(), -1);
384            return;
385        }
386        /** @var helper_plugin_approve $helper */
387        $helper = plugin_load('helper', 'approve');
388
389        //no content was changed
390        if (!$event->data['contentChanged']) return;
391
392        $changeType = $event->data['changeType'];
393        if ($changeType == DOKU_CHANGE_TYPE_REVERT) {
394            if ($event->data['oldContent'] == '') {
395                $changeType = DOKU_CHANGE_TYPE_CREATE;
396            } else {
397                $changeType = DOKU_CHANGE_TYPE_EDIT;
398            }
399        }
400
401        $id = $event->data['id'];
402        switch ($changeType) {
403            case DOKU_CHANGE_TYPE_EDIT:
404            case DOKU_CHANGE_TYPE_REVERT:
405            case DOKU_CHANGE_TYPE_MINOR_EDIT:
406                $last_change_date = $event->data['newRevision'];
407
408                //if the current page has approved or ready_for_approval -- keep it
409                $rev = $this->lastRevisionHasntApprovalData($id);
410                if ($rev) {
411                    $sqlite->query('UPDATE revision SET rev=? WHERE page=? AND rev=?',
412                        $last_change_date, $id, $rev);
413
414                } else {
415                    //keep previous record
416                    $sqlite->query('UPDATE revision SET current=0
417                                            WHERE page=?
418                                            AND current=1', $id);
419
420                    $sqlite->storeEntry('revision', [
421                        'page' => $id,
422                        'rev' => $last_change_date,
423                        'current' => 1
424                    ]);
425                }
426                break;
427            case DOKU_CHANGE_TYPE_DELETE:
428                //delete information about availability of a page but keep the history
429                $sqlite->query('DELETE FROM page WHERE page=?', $id);
430
431                //delete revision if no information about approvals
432                $rev = $this->lastRevisionHasntApprovalData($id);
433                if ($rev) {
434                    $sqlite->query('DELETE FROM revision WHERE page=? AND rev=?', $id, $rev);
435                } else {
436                    $sqlite->query('UPDATE revision SET current=0 WHERE page=? AND current=1', $id);
437                }
438
439                break;
440            case DOKU_CHANGE_TYPE_CREATE:
441                if ($helper->isPageAssigned($sqlite, $id, $newApprover)) {
442                    $data = [
443                        'page' => $id,
444                        'hidden' => $helper->in_hidden_namespace($sqlite, $id) ? '1' : '0'
445                    ];
446                    if (!blank($newApprover)) {
447                        $data['approver'] = $newApprover;
448                    }
449                    $sqlite->storeEntry('page', $data);
450                }
451
452                //store revision
453                $last_change_date = $event->data['newRevision'];
454                $sqlite->storeEntry('revision', [
455                    'page' => $id,
456                    'rev' => $last_change_date,
457                    'current' => 1
458                ]);
459                break;
460        }
461    }
462}
463