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