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