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