xref: /plugin/approve/action/approve.php (revision 4474ed8a8edd55319eb6dd1e4e69fdb09925ebf0)
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    /**
13     * @return helper_plugin_sqlite
14     */
15    public function sqlite() {
16        if (!$this->sqlite) {
17            $db_helper = plugin_load('helper', 'approve_db');
18            $this->sqlite = $db_helper->getDB();
19        }
20        return $this->sqlite;
21    }
22
23    /**
24     * @param Doku_Event_Handler $controller
25     */
26    public function register(Doku_Event_Handler $controller) {
27        $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'handle_diff_accept');
28        $controller->register_hook('HTML_SHOWREV_OUTPUT', 'BEFORE', $this, 'handle_showrev');
29        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_approve');
30        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_viewer');
31        $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_display_banner');
32        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after');
33        // ensure a page revision is created when summary changes:
34//        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before');
35//        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after');
36    }
37
38    /**
39     * @param $id
40     * @return bool
41     */
42    protected function use_approve_here($id) {
43        $res = $this->sqlite()->query('SELECT page FROM page WHERE page=? AND hidden=0', $id);
44        if ($this->sqlite()->res2single($res)) {
45            return true;
46        }
47        return false;
48    }
49
50    /**
51     * @param $id
52     * @return bool|string
53     */
54    protected function find_last_approved($id) {
55        $res = $this->sqlite()->query('SELECT rev FROM revision
56                                WHERE page=? AND approved IS NOT NULL
57                                ORDER BY rev DESC LIMIT 1', $id);
58        return $this->sqlite()->res2single($res);
59    }
60
61
62    /**
63     * @param Doku_Event $event
64     */
65    public function handle_diff_accept(Doku_Event $event) {
66		global $INFO;
67
68		if (!$this->use_approve_here($INFO['id'])) return;
69
70		if ($event->data == 'diff' && isset($_GET['approve'])) {
71		    $href = wl($INFO['id'], ['approve' => 'approve']);
72			ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>');
73		}
74
75        if ($this->getConf('ready_for_approval') && $event->data == 'diff' && isset($_GET['ready_for_approval'])) {
76            $href = wl($INFO['id'], ['ready_for_approval' => 'ready_for_approval']);
77            ptln('<a href="' . $href . '">'.$this->getLang('approve').'</a>');
78		}
79	}
80
81    /**
82     * @param Doku_Event $event
83     */
84    public function handle_showrev(Doku_Event $event) {
85        global $INFO;
86
87        if (!$this->use_approve_here($INFO['id'])) return;
88
89        $last_approved_rev = $this->find_last_approved($INFO['id']);
90		if ($last_approved_rev == $INFO['rev']) {
91            $event->preventDefault();
92        }
93	}
94
95//	function can_approve() {
96//		global $ID;
97//		return auth_quickaclcheck($ID) >= AUTH_DELETE;
98//	}
99//
100//    function can_edit() {
101//		global $ID;
102//		return auth_quickaclcheck($ID) >= AUTH_EDIT;
103//	}
104
105    /**
106     * @param Doku_Event $event
107     */
108    public function handle_approve(Doku_Event $event) {
109		global $INFO;
110
111        if (!$this->use_approve_here($INFO['id'])) return;
112
113		if ($event->data == 'show' && isset($_GET['approve']) &&
114            auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) {
115
116		    $res = $this->sqlite()->query('SELECT MAX(version)+1 FROM revision
117                                            WHERE page=?', $INFO['id']);
118		    $next_version = $this->sqlite()->res2single($res);
119		    if (!$next_version) {
120                $next_version = 1;
121            }
122		    //approved IS NULL prevents from overriding already approved page
123		    $this->sqlite()->query('UPDATE revision
124		                    SET approved=?, version=?
125                            WHERE page=? AND current=1 AND approved IS NULL',
126                            date('c'), $next_version, $INFO['id']);
127
128			header('Location: ' . wl($INFO['id']));
129		} elseif ($event->data == 'show' && isset($_GET['ready_for_approval']) &&
130            auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) {
131
132            $this->sqlite()->query('UPDATE revision SET ready_for_approval=?
133                            WHERE page=? AND current=1 AND ready_for_approval IS NULL',
134                            date('c'), $INFO['id']);
135
136            header('Location: ' . wl($INFO['id']));
137		}
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        if ($event->data != 'show') return;
149        //apply only to current page
150        if (!$INFO['rev']) return;
151        if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) return;
152        if (!$this->use_approve_here($INFO['id'])) return;
153
154        $last_approved_rev = $this->find_last_approved($INFO['id']);
155        //no page is approved
156        if (!$last_approved_rev) return;
157
158        $last_change_date = @filemtime(wikiFN($INFO['id']));
159        //current page is approved
160        if ($last_approved_rev == $last_change_date) return;
161
162	    header("Location: " . wl($INFO['id'], ['rev' => $last_approved_rev]));
163	}
164
165//	function find_lastest_approved() {
166//		global $ID;
167//		$m = p_get_metadata($ID);
168//		$sum = $m['last_change']['sum'];
169//		if ($sum == $this->getConf('sum approved'))
170//			return 0;
171//
172//		$changelog = new PageChangeLog($ID);
173//
174//		$chs = $changelog->getRevisions(0, 10000);
175//		foreach ($chs as $rev) {
176//			$ch = $changelog->getRevisionInfo($rev);
177//			if ($ch['sum'] == $this->getConf('sum approved'))
178//				return $rev;
179//		}
180//		return -1;
181//	}
182
183    /**
184     * @param Doku_Event $event
185     */
186    public function handle_display_banner(Doku_Event $event) {
187		global $INFO;
188
189        if ($event->data != 'show') return;
190        if (!$INFO['exists']) return;
191        if (!$this->use_approve_here($INFO['id'])) return;
192
193//        $last_change_date = p_get_metadata($INFO['id'], 'last_change date');
194        $last_change_date = @filemtime(wikiFN($INFO['id']));
195        $rev = !$INFO['rev'] ? $last_change_date : $INFO['rev'];
196
197
198        $res = $this->sqlite()->query('SELECT ready_for_approval, approved, version
199                                FROM revision
200                                WHERE page=? AND rev=?', $INFO['id'], $rev);
201
202        $approve = $this->sqlite()->res_fetch_assoc($res);
203
204		$classes = [];
205		if ($this->getConf('prettyprint')) {
206		    $classes[] = 'plugin__approve_noprint';
207        }
208
209        if ($approve['approved']) {
210		    $classes[] = 'plugin__approve_green';
211		} elseif ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
212		    $classes[] = 'plugin__approve_ready';
213        } else {
214            $classes[] = 'plugin__approve_red';
215        }
216
217		ptln('<div id="plugin__approve" class="' . implode(' ', $classes) . '">');
218
219		tpl_pageinfo();
220		ptln(' | ');
221
222		if ($approve['approved']) {
223			ptln('<strong>'.$this->getLang('approved').'</strong> ('
224                . $this->getLang('version') .  ': ' . $approve['version'] . ')');
225
226			//not the newest page
227			if ($rev != $last_change_date) {
228                $res = $this->sqlite()->query('SELECT rev, current FROM revision
229                                WHERE page=? AND approved IS NOT NULL
230                                ORDER BY rev DESC LIMIT 1', $INFO['id']);
231
232                $lastest_approve = $this->sqlite()->res_fetch_assoc($res);
233
234			    //we can see drafts
235                if (auth_quickaclcheck($INFO['id']) >= AUTH_EDIT) {
236                    ptln('<a href="' . wl($INFO['id']) . '">');
237                    ptln($this->getLang($lastest_approve['current'] ? 'newest_approved' : 'newest_draft'));
238                    ptln('</a>');
239                } else {
240                    $urlParameters = [];
241                    if (!$lastest_approve['current']) {
242                        $urlParameters['rev'] = $lastest_approve['rev'];
243                    }
244                    ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
245                    ptln($this->getLang('newest_approved'));
246                    ptln('</a>');
247                }
248            }
249
250		} else {
251			ptln('<span>'.$this->getLang('draft').'</span>');
252
253			if ($this->getConf('ready_for_approval') && $approve['ready_for_approval']) {
254				ptln('<span>| '.$this->getLang('marked_approve_ready').'</span>');
255			}
256
257
258            $res = $this->sqlite()->query('SELECT rev, current FROM revision
259                            WHERE page=? AND approved IS NOT NULL
260                            ORDER BY rev DESC LIMIT 1', $INFO['id']);
261
262            $lastest_approve = $this->sqlite()->res_fetch_assoc($res);
263
264
265            //not exists approve for current page
266			if (!$lastest_approve) {
267                //not the newest page
268                if ($rev != $last_change_date) {
269				    ptln('<a href="'.wl($INFO['id']).'">');
270                    ptln($this->getLang('newest_draft'));
271				    ptln('</a>');
272				}
273			} else {
274                $urlParameters = [];
275                if (!$lastest_approve['current']) {
276                    $urlParameters['rev'] = $lastest_approve['rev'];
277                }
278                ptln('<a href="' . wl($INFO['id'], $urlParameters) . '">');
279                ptln($this->getLang('newest_approved'));
280				ptln('</a>');
281			}
282
283			//we are in current page
284			if ($rev == $last_change_date) {
285
286                $last_approved_rev = 0;
287                if (isset($lastest_approve['rev'])) {
288                    $last_approved_rev = $lastest_approve['rev'];
289                }
290
291                if ($this->getConf('ready_for_approval') &&
292                    auth_quickaclcheck($INFO['id']) >= AUTH_EDIT &&
293                    !$approve['ready_for_approval']) {
294
295                    $urlParameters = [
296                        'rev' => $last_approved_rev,
297                        'do' => 'diff',
298                        'ready_for_approval' => 'ready_for_approval'
299                    ];
300                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
301                    ptln($this->getLang('approve_ready'));
302                    ptln('</a>');
303                }
304
305                if (auth_quickaclcheck($INFO['id']) >= AUTH_DELETE) {
306
307                    $urlParameters = [
308                        'rev' => $last_approved_rev,
309                        'do' => 'diff',
310                        'approve' => 'approve'
311                    ];
312                    ptln(' | <a href="'.wl($INFO['id'], $urlParameters).'">');
313                    ptln($this->getLang('approve'));
314                    ptln('</a>');
315                }
316
317            }
318		}
319		ptln('</div>');
320	}
321
322    /**
323     * @return bool|string
324     */
325    protected function prev_rev($id) {
326        $res = $this->sqlite()->query('SELECT rev FROM revision
327                                        WHERE page=?
328                                          AND current=1
329                                          AND approved IS NULL
330                                          AND ready_for_approval IS NULL', $id);
331
332        return $this->sqlite()->res2single($res);
333    }
334
335    /**
336     *
337     * @param Doku_Event $event  event object by reference
338     * @return void
339     */
340    public function handle_pagesave_after(Doku_Event $event) {
341        //no content was mde
342        if (!$event->data['contentChanged']) return;
343
344        $changeType = $event->data['changeType'];
345        if ($changeType == DOKU_CHANGE_TYPE_REVERT) {
346            if ($event->data['oldContent'] == '') {
347                $changeType = DOKU_CHANGE_TYPE_CREATE;
348            } else {
349                $changeType = DOKU_CHANGE_TYPE_EDIT;
350            }
351        }
352
353        $id = $event->data['id'];
354        //TODO: if created, if deleted
355        switch ($changeType) {
356            case DOKU_CHANGE_TYPE_EDIT:
357            case DOKU_CHANGE_TYPE_REVERT:
358            case DOKU_CHANGE_TYPE_MINOR_EDIT:
359                $last_change_date = $event->data['newRevision'];
360
361                //if the current page has approved or ready_for_approval -- keep it
362                $prev_rev = $this->prev_rev($id);
363                if ($prev_rev) {
364                    $this->sqlite()->query('UPDATE revision SET rev=? WHERE page=? AND rev=?',
365                        $last_change_date, $id, $prev_rev);
366
367                } else {
368                    //keep previous record
369                    $this->sqlite()->query('UPDATE revision SET current=0
370                                            WHERE page=?
371                                            AND current=1', $id);
372
373                    $this->sqlite()->storeEntry('revision', [
374                        'page' => $id,
375                        'rev' => $last_change_date,
376                        'current' => 1
377                    ]);
378                }
379                break;
380            case DOKU_CHANGE_TYPE_DELETE:
381                //delete information about availability of a page but keep the history
382                $this->sqlite()->query('DELETE FROM page WHERE page=?', $id);
383
384                //delete revision if no information about approvals
385                $prev_rev = $this->prev_rev($id);
386                if ($prev_rev) {
387                    $this->sqlite()->query('DELETE FROM revision WHERE page=? AND rev=?',
388                        $id, $prev_rev);
389                } else {
390                    $this->sqlite()->query('UPDATE revision SET current=0 WHERE page=? AND rev=?',
391                        $id, $prev_rev);
392                }
393
394                break;
395            case DOKU_CHANGE_TYPE_CREATE:
396                $last_change_date = $event->data['newRevision'];
397                //TODO should be checked against no_approve_rev
398                $this->sqlite()->storeEntry('page', [
399                    'page' => $id
400                ]);
401
402                $this->sqlite()->storeEntry('revision', [
403                    'page' => $id,
404                    'rev' => $last_change_date,
405                    'current' => 1
406                ]);
407                break;
408        }
409    }
410
411    /**
412     * Check if the page has to be changed
413     *
414     * @param Doku_Event $event  event object by reference
415     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
416     *                           handler was registered]
417     * @return void
418     */
419//    public function handle_pagesave_before(Doku_Event $event, $param) {
420//        global $REV;
421//        $id = $event->data['id'];
422//        if (!$this->hlp->use_approve_here($id)) return;
423//
424//        //save page if summary is provided
425//        if($event->data['summary'] == $this->getConf('sum approved') ||
426//            $event->data['summary'] == $this->getConf('sum ready for approval')) {
427//            $event->data['contentChanged'] = true;
428//        }
429//    }
430
431    /**
432     * @param Doku_Event $event
433     * @param            $param
434     */
435//    public function handle_pagesave_after(Doku_Event $event, $param) {
436//        global $REV;
437//        $id = $event->data['id'];
438//        if (!$this->hlp->use_approve_here($id)) return;
439//
440//        //save page if summary is provided
441//        if($event->data['summary'] == $this->getConf('sum approved')) {
442//
443//            $versions = p_get_metadata($id, ApproveConst::METADATA_VERSIONS_KEY);
444//            //calculate versions
445//            if (!$versions) {
446//                $this->render_metadata_for_approved_page($id, $event->data['newRevision']);
447//            } else {
448//                $curver = $versions[0] + 1;
449//                $versions[0] = $curver;
450//                $versions[$event->data['newRevision']] = $curver;
451//                p_set_metadata($id, array(ApproveConst::METADATA_VERSIONS_KEY => $versions));
452//            }
453//        }
454//    }
455
456
457    /**
458     * Calculate current version
459     *
460     * @param $id
461     * @return array
462     */
463//    protected function render_metadata_for_approved_page($id, $currev=false) {
464//        if (!$currev) $currev = @filemtime(wikiFN($id));
465//
466//        $version = $this->approved($id);
467//        //version for current page
468//        $curver = $version + 1;
469//        $versions = array(0 => $curver, $currev => $curver);
470//
471//        $changelog = new PageChangeLog($id);
472//        $first = 0;
473//        $num = 100;
474//        while (count($revs = $changelog->getRevisions($first, $num)) > 0) {
475//            foreach ($revs as $rev) {
476//                $revInfo = $changelog->getRevisionInfo($rev);
477//                if ($revInfo['sum'] == $this->getConf('sum approved')) {
478//                    $versions[$rev] = $version;
479//                    $version -= 1;
480//                }
481//            }
482//            $first += $num;
483//        }
484//
485//        p_set_metadata($id, array(ApproveConst::METADATA_VERSIONS_KEY => $versions));
486//
487//        return $versions;
488//    }
489
490    /**
491     * Get the number of approved pages
492     * @param $id
493     * @return int
494     */
495//    protected function approved($id) {
496//        $count = 0;
497//
498//        $changelog = new PageChangeLog($id);
499//        $first = 0;
500//        $num = 100;
501//        while (count($revs = $changelog->getRevisions($first, $num)) > 0) {
502//            foreach ($revs as $rev) {
503//                $revInfo = $changelog->getRevisionInfo($rev);
504//                if ($revInfo['sum'] == $this->getConf('sum approved')) {
505//                    $count += 1;
506//                }
507//            }
508//            $first += $num;
509//        }
510//
511//        return $count;
512//    }
513}
514