1<?php
2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
3if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
4
5require_once(DOKU_PLUGIN.'admin.php');
6require_once(DOKU_INC.'inc/pageutils.php');
7require_once(DOKU_INC.'inc/changelog.php');
8require_once(DOKU_INC.'inc/io.php');
9require_once(DOKU_INC.'inc/common.php');
10
11/**
12 * This plugin is used to cleanup the history
13 *
14 * @see    http://dokuwiki.org/plugin:clearhistory
15 * @author Dominik Eckelmann <deckelmann@gmail.com>
16 */
17class admin_plugin_clearhistory extends DokuWiki_Admin_Plugin {
18
19    /**
20     * pages in a hiracical view
21     *
22     * "<namespace>" => array( ... )
23     * "pages" => array ( pagenames )
24     */
25    var $pages = array();
26
27	/**
28	 * counts deleted pages for a run
29	 */
30    var $delcounter = 0;
31
32    /**
33     * return some information about the plugin
34	 *
35	 * @return array
36     */
37    function getInfo(){
38      return confToHash(dirname(__FILE__).'/plugin.info.txt');
39    }
40
41    /**
42     * return sort order for position in admin menu
43	 *
44	 * @return integer
45     */
46    function getMenuSort() {
47      return 999;
48    }
49
50    /**
51     * handle the request befor html output
52	 *
53	 * @see html()
54     */
55    function handle() {
56        $onlySmall = false;
57        $onlsNoComment = false;
58        if (isset($_REQUEST['onlysmall']) && $_REQUEST['onlysmall'] == 'on' ) $onlySmall = true;
59        if (isset($_REQUEST['onlynocomment']) && $_REQUEST['onlynocomment'] == 'on' ) $onlyNoComment = true;
60
61        if (isset($_GET['clear'])) {
62            if ($_GET['clear'] == 1) {
63                $this->_scanRecents( 30 , $onlySmall , $onlyNoComment );
64            } else if ($_GET['clear'] == 2) {
65                $_GET['ns'] = cleanID($_GET['ns']);
66                $this->_scanNamespace($_GET['ns'] , $onlySmall , $onlyNoComment );
67            }
68            msg(sprintf($this->getLang('deleted'),$this->delcounter),1);
69        }
70    }
71
72    /**
73     * output html for the admin page
74     */
75    function html() {
76        echo '<h1>'.$this->getLang('name').'</h1>';
77        echo '<form action="doku.php" method="GET"><fieldset class="clearhistory">';
78        echo '<input type="hidden" name="do" value="admin" />';
79        echo '<input type="hidden" name="page" value="clearhistory" />';
80
81        echo '<fieldset><input type="radio" name="clear" value="1" id="c1" checked="checked" /> <label for="c1">';
82        echo $this->getLang('clean on recent changes').'</label></fieldset><br/>';
83
84        echo '<fieldset><input type="radio" name="clear" value="2" id="c2" /> <label for="c2">';
85        echo $this->getLang('clean on namespace');
86        echo '</label><br /><input type="text" name="ns" class="edit" />';
87        echo '</fieldset><br/>';
88		echo '<fieldset>';
89		echo '<input type="checkbox" name="onlysmall" id="c3" '. ($this->getConf('autoclearonlysmall')?'checked="checked"':'') .'  /> <label for="c3">'.$this->getLang('onlysmall').'</label><br />';
90		echo '<input type="checkbox" name="onlynocomment" id="c4" '. ($this->getConf('autoclearonlynocomment')?'checked="checked"':'') .' /> <label for="c4">'.$this->getLang('onlynocomment').'</label><br />';
91		echo '</fieldset>';
92
93        echo '<input type="submit" value="'.$this->getLang('do').'" class="button" /></fieldset>';
94        echo '</form>';
95        echo '<p class="clearhistory">'.$this->getLang('desctext').'</p>';
96    }
97
98	/**
99	 * Scans throu a namespace and count the deleted pages in $this->delcounter
100	 *
101     * @param string	$ns 			the namespace to search in
102	 * @param boolean	$onlySmall 		only delete small changes on true
103	 * @param boolean	$onlyNoComment	don't delete changes with a comment on true
104	 */
105	function _scanNamespace($ns, $onlySmall = false, $onlyNoComment = false) {
106		$this->delcounter = 0;
107		$this->_scan($ns, $onlySmall, $onlyNoComment);
108	}
109
110
111    /**
112     * Scans namespaces for deletable revisions
113	 *
114     * @param string	$ns				the namespace to search in
115	 * @param boolean	$onlySmall 		only delete small changes on true
116	 * @param boolean	$onlyNoComment	don't delete changes with a comment on true
117     */
118    function _scan($ns = '', $onlySmall = false, $onlyNoComment = false) {
119        $dir = preg_replace('/\.txt(\.gz)?/i','', wikiFN($ns));
120        $dir = rtrim($dir,'/');
121        if (!is_dir($dir)) return;
122        $dh = opendir($dir);
123        if (!$dh) {
124            echo 'error';
125            return;
126        }
127        while (($file = readdir($dh)) !== false) {
128            if ($file == '.' || $file == '..' ) continue;
129            if (is_dir($dir.'/'.$file)) {
130                $this->_scan($ns.':'.$file);
131                continue;
132            }
133            if ($file[0] == '_') continue;
134            if (substr($file,-4) == '.txt') {
135                $name = substr($file,0,-4);
136                $this->_parseChangesFile(metaFN($ns.':'.$name,'.changes'),$ns.':'.$name, $onlySmall, $onlyNoComment);
137            }
138        }
139        closedir($dh);
140    }
141
142    /**
143     * Scans the recent changed files for changes
144	 *
145     * @param int 		$num 			number of last changed files to scan
146	 * @param boolean	$onlySmall 		only delete small changes on true
147	 * @param boolean	$onlyNoComment	don't delete changes with a comment on true
148     */
149    function _scanRecents( $num = 30, $onlySmall = false , $onlyNoComment = false ) {
150        $recents = getRecents(0,$num);
151
152        $this->delcounter = 0;
153        foreach ($recents as $recent) {
154            $this->_parseChangesFile(metaFN($recent['id'],'.changes'),$recent['id'], $onlySmall, $onlyNoComment);
155        }
156    }
157
158    /**
159     * Parses a .changes file for deletable pages and deletes them.
160	 *
161     * @param string	$file			the path to the change file
162	 * @param string	$page			wiki pagename
163	 * @param boolean	$onlySmall		deletes only small changes
164	 * @param boolean	$onlyNoComment	deletes only entrys without a comment
165     */
166    function _parseChangesFile( $file , $page , $onlySmall = false , $onlyNoComment = false ) {
167        if (!is_file($file)) return;
168        if (checklock($page)) return;
169        lock($page);
170        $content = file_get_contents($file);
171        // get page informations
172        $max = preg_match_all('/^([0-9]+)\s+?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+?(E|C|D|R)\s+?(\S+)\s+?(\S*)\s+?(.*)$/im',$content,$match);
173        if ($max <= 1) return;
174        // set mark to creation entry
175        $cmptime = $match[1][$i]+9999999999;
176        $cmpuser = (empty($match[5][$max-1]))?$match[2][$max-1]:$match[5][$max-1]; // user or if not logged in ip
177        $newcontent = '';
178        for ($i=$max-1;$i>=0;$i--) {
179            $user = (empty($match[5][$i]))?$match[2][$i]:$match[5][$i]; // user or if not logged in ip
180            $time = $match[1][$i];
181            // Creations arnt touched
182            if ($match[3][$i] != "E" && $match[3][$i] != "e") {
183                $cmpuser = $user;
184                $cmptime = $time;
185                $newcontent = $this->_addLine($match,$i) . $newcontent;
186                continue;
187            }
188            // if not the same user -> new mark and continue
189            if ($user != $cmpuser) {
190                $cmpuser = $user;
191                $cmptime = $time;
192                $newcontent = $this->_addLine($match,$i) . $newcontent;
193                continue;
194            }
195
196			// if onlySmall is set we just want to handle the small matches
197			if ($onlySmall && $match[3][$i] == 'E') {
198				$cmpuser = $user;
199				$cmptime = $time;
200				$newcontent = $this->_addLine($match,$i) . $newcontent;
201				continue;
202			}
203
204			// if onlyNoComment is set we pass all lines with a comment
205			if ($onlyNoComment && trim($match[6][$i]) != '') {
206				$cmpuser = $user;
207				$cmptime = $time;
208				$newcontent = $this->_addLine($match,$i) . $newcontent;
209				continue;
210			}
211
212            // check the time difference between the entrys
213            if ( $cmptime-(60*60) < $time ) {
214                @unlink(wikiFN($match[4][$i],$time));
215                $this->delcounter++;
216                continue;
217            }
218            $cmptime = $time;
219            $newcontent = $this->_addLine($match,$i) . $newcontent;
220        }
221        unlock($page);
222        io_saveFile($file,$newcontent);
223    }
224
225	function _addLine($match,$i) {
226		return $match[0][$i]."\n";
227	}
228
229	/**
230	 * shows that this function is accessible only by admins
231	 *
232	 * @return true
233	 */
234    function forAdminOnly() {
235        return true;
236    }
237
238	/**
239	 * returns the name in the menu
240	 *
241	 * @return Menu name for the plugin
242	 */
243    function getMenuText($lang) {
244        return $this->getLang('menu');
245    }
246
247}
248
249?>
250