1<?php
2/**
3 * RevisionsDue Plugin: Find pages that should be revised as specified by the <revision_frequency>YYY</revision_frequency> xml.
4 * syntax ~~REVISIONS:<choice>~~  <choice> :: all
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     <Stephan@SparklingSoftware.com.au>
7 * @author     Stephan Dekker <Stephan@SparklingSoftware.com.au>
8 */
9
10if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'syntax.php');
13
14require_once(DOKU_INC.'inc/search.php');
15
16define('DEBUG', 0);
17
18function revision_callback_search_wanted(&$data,$base,$file,$type,$lvl,$opts) {
19    global $conf;
20
21	if($type == 'd'){
22		return true; // recurse all directories, but we don't store namespaces
23	}
24
25    if(!preg_match("/.*\.txt$/", $file)) {  // Ignore everything but TXT
26		return true;
27	}
28
29	// get id of this file
30	$id = pathID($file);
31
32	//check ACL
33	if(auth_quickaclcheck($id) < AUTH_READ) {
34		return false;
35	}
36
37    $revision_frequency = get_revision_frequency($file);
38
39    // try to avoid making duplicate entries for forms and pages
40	$item = &$data["$id"];
41	if(! isset($item)) {
42       // Create a new entry
43       $filename = $conf['datadir'].$file;
44       $last_modified = filemtime($filename);
45
46       $revision_date = $last_modified + (intval($revision_frequency) * 86400);
47	   if ($revision_date < time()) {
48           $data["$id"]=array('revision' => $last_modified,
49                'frequency' => $revision_frequency,
50                'revision_date' => $revision_date );
51       }
52       else
53       {
54           // The item is actually already added to the array! We need to remove it if we don't need it.
55           unset($data["$id"]);
56       }
57	}
58
59	return true;
60}
61
62function revision_string($revision) {
63
64  $result = date("Y-m-d", $revision);
65  return $result;
66}
67
68function get_revision_frequency($file) {
69    global $conf;
70
71  $filename = $conf['datadir'].$file;
72  $body = @file_get_contents($filename);
73  $pattern = '/<revision_frequency>\-?\d+<\/revision_frequency>/i';
74  $count = preg_match($pattern, $body, $matches);
75  $result = htmlspecialchars($matches[0]);
76  $result = str_replace(htmlspecialchars('<revision_frequency>'), "", $result);
77  $result = str_replace(htmlspecialchars('</revision_frequency>'), "", $result);
78
79  return $result;
80}
81
82function date_compare($a, $b) {
83    if ($a['revision_date'] == $b['revision_date']) {
84       return 0;
85    }
86    return ($a['revision_date'] < $b['revision_date']) ? -1 : 1;
87}
88
89
90/**
91 * All DokuWiki plugins to extend the parser/rendering mechanism
92 * need to inherit from this class
93 */
94class syntax_plugin_revisionsdue extends DokuWiki_Syntax_Plugin {
95    /**
96     * return some info
97     */
98    function getInfo(){
99        return array(
100            'author' => 'Stephan Dekker',
101            'email'  => 'Stephan@SparklingSoftware.com.au',
102            'date'   => @file_get_contents(dirname(__FILE__) . '/VERSION'),
103            'name'   => 'RevisionsDue Plugin',
104            'desc'   => 'Find pages that should be revised as specified by the <revision_frequency>YYY</revision_frequency> xml.
105            syntax ~~REVISIONS:<choice>~~ .
106            <choice> :: all',
107            'url'    => 'http://dokuwiki.org/plugin:revisionsdue',
108        );
109    }
110
111    /**
112     * What kind of syntax are we?
113     */
114    function getType(){
115        return 'substition';
116    }
117
118    /**
119     * What about paragraphs?
120     */
121    function getPType(){
122        return 'normal';
123    }
124
125    /**
126     * Where to sort in?
127     */
128    function getSort(){
129        return 990;     //was 990
130    }
131
132
133    /**
134     * Connect pattern to lexer
135     */
136    function connectTo($mode) {
137        $this->Lexer->addSpecialPattern('~~REVISIONS:[0-9a-zA-Z_:!]+~~',$mode,'plugin_revisionsdue');
138    }
139
140    /**
141     * Handle the match
142     */
143
144    function handle($match, $state, $pos, &$handler){
145        $match_array = array();
146        $match = substr($match,12,-2); //strip ~~REVISIONS: from start and ~~ from end
147        // Wolfgang 2007-08-29 suggests commenting out the next line
148        // $match = strtolower($match);
149        //create array, using ! as separator
150        $match_array = explode("!", $match);
151        // $match_array[0] will be all, or syntax error
152        // this return value appears in render() as the $data param there
153        return $match_array;
154    }
155
156    /**
157     * Create output
158     */
159    function render($format, &$renderer, $data) {
160        global $INFO, $conf;
161
162        if($format == 'xhtml'){
163
164			// user needs to add ~~NOCACHE~~ manually to page, to assure ACL rules are followed
165			// coding here is too late, it doesn't get parsed
166			// $renderer->doc .= "~~NOCACHE~~";
167
168            // $data is an array
169            // $data[1]..[x] are excluded namespaces, $data[0] is the report type
170            //handle choices
171            switch ($data[0]){
172                case 'all':
173                    $renderer->doc .= $this->all_pages($data);
174                    break;
175                case 'revisions':
176                    $renderer->doc .= $this->all_pages($data, false);
177                    break;
178                default:
179                    $renderer->doc .= "RevisionsDue syntax error";
180                   // $renderer->doc .= "syntax ~~REVISIONS:<choice>~~<choice> :: all  Example: ~~REVISIONS:all~~";
181            }
182
183             return true;
184        }
185        return false;
186    }
187
188    function all_pages($params_array, $mandatory_revisions = true ) {
189      global $conf;
190      $result = '';
191      $data = array();
192      search($data,$conf['datadir'],'revision_callback_search_wanted',array('ns' => $ns));
193
194      $result .= $this->revision_report_table($data, $mandatory_revisions);
195
196      return $result;
197    }
198
199  function revision_report_table( $data, $mandatory_revisions = true )
200  {
201    global $conf;
202
203    $count = 1;
204    $output = '';
205
206    // for valid html - need to close the <p> that is feed before this
207    $output .= '</p>';
208    $output .= '<table class="inline"><tr><th> # </th><th>Title</th><th>Last Revision</th><th>Frequency</th><th>Next Revision Date</th></tr>'."\n" ;
209
210    uasort($data, 'date_compare');
211
212  	foreach($data as $id=>$item)
213    {
214  		if( $item['revision'] === '' ) {
215         continue ;
216        }
217
218        if ($mandatory_revisions === false and ($item['frequency'] === '' or !isset($item['frequency']))) {
219              continue ;
220        }
221
222  		// $id is a string, looks like this: page, namespace:page, or namespace:<subspaces>:page
223  		$match_array = explode(":", $id);
224  		//remove last item in array, the page identifier
225  		$match_array = array_slice($match_array, 0, -1);
226  		//put it back together
227  		$page_namespace = implode (":", $match_array);
228  		//add a trailing :
229  		$page_namespace = $page_namespace . ':';
230
231  		//set it to show, unless blocked by exclusion list
232  		$show_it = true;
233  		foreach ($exclude_array as $exclude_item)
234      {
235  			//add a trailing : to each $item too
236  			$exclude_item = $exclude_item . ":";
237  			// need === to avoid boolean false
238  			// strpos(haystack, needle)
239  			// if exclusion is beginning of page's namespace , block it
240  			if (strpos($page_namespace, $exclude_item) === 0){
241  			   //there is a match, so block it
242  			   $show_it = false;
243  			}
244  		}
245
246  		if( $show_it )
247      {
248   	    $revision = $item['revision'];
249   	    $frequency = $item['frequency'];
250   	    $revision_date = $item['revision_date'];
251
252//        msg('Creating teable for:'.$id.' was modified: '.date ("F d Y H:i:s.", $revision));
253
254        $output .=  "<tr><td>$count</td><td><a href=\"". wl($id)
255        . "\" class=\"" . "wikilink1"
256        . "\"  onclick=\"return svchk()\" onkeypress=\"return svchk()\">"
257        . $id .'</a></td><td>'.revision_string($revision)."</td><td>".$frequency."</td><td>".revision_string($revision_date)."</td></tr>\n";
258
259  			$count++;
260  		}
261
262  	}
263
264  	$output .=  "</table>\n";
265  	//for valid html = need to reopen a <p>
266  	$output .= '<p>';
267
268    return $output;
269  }
270
271
272}
273
274
275//Setup VIM: ex: et ts=4 enc=utf-8 :
276?>
277