xref: /plugin/approve/action/approve.php (revision c7d53eaba29e207f5d28ea550cd66a04c154a49d)
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		/** @var DokuWiki_Auth_Plugin $auth */
150		global $auth;
151
152        if ($event->data != 'show') return;
153        if (!$INFO['exists']) return;
154        if (!$this->helper()->use_approve_here($INFO['id'], $maintainer)) return;
155
156//        $last_change_date = p_get_metadata($INFO['id'], 'last_change date');
157        $last_change_date = @filemtime(wikiFN($INFO['id']));
158        $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev'];
159
160
161        $res = $this->sqlite()->query('SELECT ready_for_approval, ready_for_approval_by,
162                                        approved, approved_by, version
163                                FROM revision
164                                WHERE page=? AND rev=?', $INFO['id'], $rev);
165
166        $approve = $this->sqlite()->res_fetch_assoc($res);
167
168		$classes = [];
169		if ($this->getConf('prettyprint')) {
170		    $classes[] = 'plugin__approve_noprint';
171        }
172
173        if ($approve['approved']) {
174		    $classes[] = 'plugin__approve_green';
175		} elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
176		    $classes[] = 'plugin__approve_ready';
177        } else {
178            $classes[] = 'plugin__approve_red';
179        }
180
181		ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">');
182
183//		tpl_pageinfo();
184//		ptln(' | ');
185
186		if ($approve['approved']) {
187			ptln('<strong>'.$this->getLang('approved').'</strong>');
188            ptln(' ' . dformat(strtotime($approve['approved'])));
189            $user = $auth->getUserData($approve['approved_by']);
190            ptln(' ' . $this->getLang('by') . ' ' . $user['name']);
191            ptln(' (' . $this->getLang('version') .  ': ' . $approve['version'] . ')');
192
193			//not the newest page
194			if ($rev != $last_change_date) {
195                $res = $this->sqlite()->query('SELECT rev, current FROM revision
196                                WHERE page=? AND approved IS NOT NULL
197                                ORDER BY rev DESC LIMIT 1', $INFO['id']);
198
199                $last_approve = $this->sqlite()->res_fetch_assoc($res);
200
201			    //we can see drafts
202                if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) {
203                    ptln('<a href="' . wl($INFO['id']) . '">');
204                    ptln($this->getLang($last_approve['current'] ? 'newest_approved' : 'newest_draft'));
205                    ptln('</a>');
206                //we cannot see link to draft but there is some newer approved version
207                } elseif ($last_approve['rev'] != $rev) {
208                    $urlParameters = [];
209                    if (!$last_approve['current']) {
210                        $urlParameters['rev'] = $last_approve['rev'];
211                    }
212                    ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
213                    ptln($this->getLang('newest_approved'));
214                    ptln('</a>');
215                }
216            }
217
218		} else {
219		    if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
220				ptln('<strong>'.$this->getLang('marked_approve_ready').'</strong>');
221                ptln(' ' . dformat(strtotime($approve['ready_for_approval'])));
222                $user = $auth->getUserData($approve['ready_for_approval_by']);
223                ptln(' ' . $this->getLang('by') . ' ' . $user['name']);
224			} else {
225                ptln('<strong>'.$this->getLang('draft').'</strong>');
226            }
227
228
229            $res = $this->sqlite()->query('SELECT rev, current FROM revision
230                            WHERE page=? AND approved IS NOT NULL
231                            ORDER BY rev DESC LIMIT 1', $INFO['id']);
232
233            $last_approve = $this->sqlite()->res_fetch_assoc($res);
234
235
236            //not exists approve for current page
237			if (!$last_approve) {
238                //not the newest page
239                if ($rev != $last_change_date) {
240				    ptln('<a href="'.wl($INFO['id']).'">');
241                    ptln($this->getLang('newest_draft'));
242				    ptln('</a>');
243				}
244			} else {
245                $urlParameters = [];
246                if (!$last_approve['current']) {
247                    $urlParameters['rev'] = $last_approve['rev'];
248                }
249                ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
250                ptln($this->getLang('newest_approved'));
251				ptln('</a>');
252			}
253
254			//we are in current page
255			if ($rev == $last_change_date) {
256
257			    //compare with the last approved page or 0 if there is no approved versions
258                $last_approved_rev = 0;
259                if (isset($last_approve['rev'])) {
260                    $last_approved_rev = $last_approve['rev'];
261                }
262
263                if ($this->getConf('ready_for_approval') &&
264                    auth_quickaclcheck($INFO['id']) >= AUTH_EDIT &&
265                    !$approve['ready_for_approval']) {
266
267                    $urlParameters = [
268                        'rev' => $last_approved_rev,
269                        'do' => 'diff',
270                        'ready_for_approval' => 'ready_for_approval'
271                    ];
272                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
273                    ptln($this->getLang('approve_ready'));
274                    ptln('</a>');
275                }
276
277                if (auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) {
278
279                    $urlParameters = [
280                        'rev' => $last_approved_rev,
281                        'do' => 'diff',
282                        'approve' => 'approve'
283                    ];
284                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
285                    ptln($this->getLang('approve'));
286                    ptln('</a>');
287                }
288            }
289		}
290
291		if ($maintainer) {
292            $user = $auth->getUserData($maintainer);
293            ptln(' | ' . $this->getLang('maintainer') . ': ' . $user['name']);
294        }
295
296		ptln('</div>');
297	}
298
299    /**
300     * @return bool|string
301     */
302    protected function prev_rev($id) {
303        $res = $this->sqlite()->query('SELECT rev FROM revision
304                                        WHERE page=?
305                                          AND current=1
306                                          AND approved IS NULL
307                                          AND ready_for_approval IS NULL', $id);
308
309        return $this->sqlite()->res2single($res);
310    }
311
312    /**
313     *
314     * @param Doku_Event $event  event object by reference
315     * @return void
316     */
317    public function handle_pagesave_after(Doku_Event $event) {
318        //no content was changed
319        if (!$event->data['contentChanged']) return;
320
321        $changeType = $event->data['changeType'];
322        if ($changeType == DOKU_CHANGE_TYPE_REVERT) {
323            if ($event->data['oldContent'] == '') {
324                $changeType = DOKU_CHANGE_TYPE_CREATE;
325            } else {
326                $changeType = DOKU_CHANGE_TYPE_EDIT;
327            }
328        }
329
330        $id = $event->data['id'];
331        switch ($changeType) {
332            case DOKU_CHANGE_TYPE_EDIT:
333            case DOKU_CHANGE_TYPE_REVERT:
334            case DOKU_CHANGE_TYPE_MINOR_EDIT:
335                $last_change_date = $event->data['newRevision'];
336
337                //if the current page has approved or ready_for_approval -- keep it
338                $prev_rev = $this->prev_rev($id);
339                if ($prev_rev) {
340                    $this->sqlite()->query('UPDATE revision SET rev=? WHERE page=? AND rev=?',
341                        $last_change_date, $id, $prev_rev);
342
343                } else {
344                    //keep previous record
345                    $this->sqlite()->query('UPDATE revision SET current=0
346                                            WHERE page=?
347                                            AND current=1', $id);
348
349                    $this->sqlite()->storeEntry('revision', [
350                        'page' => $id,
351                        'rev' => $last_change_date,
352                        'current' => 1
353                    ]);
354                }
355                break;
356            case DOKU_CHANGE_TYPE_DELETE:
357                //delete information about availability of a page but keep the history
358                $this->sqlite()->query('DELETE FROM page WHERE page=?', $id);
359
360                //delete revision if no information about approvals
361                $prev_rev = $this->prev_rev($id);
362                if ($prev_rev) {
363                    $this->sqlite()->query('DELETE FROM revision WHERE page=? AND rev=?',
364                        $id, $prev_rev);
365                } else {
366                    $this->sqlite()->query('UPDATE revision SET current=0 WHERE page=? AND rev=?',
367                        $id, $prev_rev);
368                }
369
370                break;
371            case DOKU_CHANGE_TYPE_CREATE:
372                $last_change_date = $event->data['newRevision'];
373                $hidden = $this->helper()->in_hidden_namespace($id);
374
375                $this->sqlite()->storeEntry('page', [
376                    'page' => $id,
377                    'hidden' => $hidden
378                ]);
379
380                $this->sqlite()->storeEntry('revision', [
381                    'page' => $id,
382                    'rev' => $last_change_date,
383                    'current' => 1
384                ]);
385                break;
386        }
387    }
388}
389