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