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