1<?php
2/**
3 * DokuWiki Plugin translationbuddy (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  H�kan Sandell <sandell.hakan@gmail.com>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) die();
11
12if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15
16require_once DOKU_PLUGIN.'syntax.php';
17
18class syntax_plugin_translationbuddy extends DokuWiki_Syntax_Plugin {
19    public function getType() {
20        return 'substition';
21    }
22
23    public function getPType() {
24        return 'block';
25    }
26
27    public function getSort() {
28        return 99;
29    }
30
31    public function connectTo($mode) {
32        $this->Lexer->addSpecialPattern('----+ *translationbuddy *-+\n.*?\n----+',$mode,'plugin_translationbuddy');
33        }
34
35    public function handle($match, $state, $pos, &$handler){
36        return $this->parseData($match);
37    }
38
39    public function render($mode, &$renderer, $data) {
40        if($mode == 'xhtml') {
41            return $this->_showData($renderer,$data);
42        }
43        return false;
44    }
45
46    /**
47     * Parse syntax data block, return keyed array of values
48     *
49     *  You may use the # character to add comments to the block.
50     *  Those will be ignored and will neither be displayed nor saved.
51     *  If you need to enter # as data, escape it with a backslash (\#).
52     *  If you need a backslash, escape it as well (\\)
53     */
54    function parseData($match){
55        // get lines
56        $lines = explode("\n",$match);
57        array_pop($lines);
58        array_shift($lines);
59
60        // parse info
61        $data = array();
62        foreach ( $lines as $line ) {
63            // ignore comments and bullet syntax
64            $line = preg_replace('/(?<![&\\\\])#.*$/','',$line);
65            $line = preg_replace('/^  \* /','',$line);
66            $line = str_replace('\\#','#',$line);
67            $line = trim($line);
68            if(empty($line)) continue;
69            list($key,$value) = preg_split('/\s*:\s*/',$line,2);
70            $key = strtolower($key);
71            if ($data[$key]){
72                $data[$key] .= ' ' . trim($value);
73            }else{
74                $data[$key] = trim($value);
75            }
76        }
77        return $data;
78    }
79
80    function _showData(&$R, $data){
81        global $ID, $conf;
82
83        // default settings
84        if (!is_numeric($data['daysnew'])) $data['daysnew'] = 14;
85        if (!is_numeric($data['outdated'])) $data['outdated'] = 5;
86        if ($data['ignore_ns']) {
87            $data['ignore_ns'] = str_replace(',', ' ', $data['ignore_ns']);
88            $ignore_ns = explode(' ', $data['ignore_ns']);
89            $ignore_ns = array_filter($ignore_ns);
90        } else {
91            $ignore_ns = array();
92        }
93        if ($data['langs']) {
94            $data['langs'] = str_replace(',', ' ', $data['langs']);
95            $langs = explode(' ', $data['langs']);
96            $langs = array_filter($langs);
97        } elseif ($conf['plugin'] && $conf['plugin']['translation']) {
98            $langs = explode(' ', $conf['plugin']['translation']['translations']);
99            $langs = array_filter($langs);
100        } else {
101            $langs = array();
102        }
103
104        // get all pages
105        $pages = idx_get_indexer()->getPages();
106        $items = array();
107        $new_pages = array();
108        foreach($pages as $id){
109            //skip hidden, non existing and restricted files
110            if(isHiddenPage($id)) continue;
111            if(auth_aclcheck($id,'','') < AUTH_READ) continue;
112
113            $page = $id;
114            $ns = $this->baseNS($page);
115            $lc = 'en';
116            if ($ns && in_array($ns,$langs)) {
117                $lc = $ns;
118                $page = $this->skipBaseNS($page);
119                $ns = $this->baseNS($page);
120            }
121            if (!$ns) $ns = '-';
122
123            $fn = wikiFN($id);
124            $date = @filemtime($fn);
125            if($date && !in_array($ns, $ignore_ns)) {
126
127                $items[$lc][$ns][$page] = $date;
128                if (filectime($fn) > time() - 60*60*24*$data['daysnew']) {
129                    $new_pages[] = $id;
130                }
131            }
132        }
133        sort($new_pages);
134
135        // new page report
136        $R->doc .= '<b>New pages last '.hsc($data['daysnew']).' days</b>';
137        $R->listu_open();
138        foreach ($new_pages as $page) {
139            $R->listitem_open(1);
140            $R->listcontent_open();
141            $R->doc .= '<a href="'.wl($page).'">'.$page.'</a>';
142            $R->listcontent_close();
143            $R->listitem_close();
144        }
145        $R->listu_close();
146        $R->doc .= '<b>Translation status</b>';
147
148        // translation effort report
149        $R->doc .= '<table>';
150        $R->doc .= '<tr><th>Lang</th><th>Namespace</th><th>Pages</th><th>Translated</th><th>Details</th></tr>';
151        foreach ($items as $lc => $item) {
152            $pageTotal = 0;
153            $first = true;
154            foreach ($item as $ns => $pages) {
155                $R->doc .= '<tr>';
156                if ($first) {
157                    $first = false;
158                    $R->doc .= '<td rowspan="'.count($item).'" id="translationbuddy__'.$lc.'">'.$lc.'</td>';
159                    $R->toc_additem('translationbuddy__'.$lc, $lc, $R->lastlevel+1);
160                }
161                $idx = ($ns=='-' ? ($lc=='en'?'-1':$lc) : ($lc=='en'?'':$lc.':').$ns);
162                $R->doc .= '<td><a href="'.wl($ID,'idx='.rawurlencode($idx)).'" >'.$ns.'</a></td>';
163                $R->doc .= '<td class="rightalign">'.count($pages).'</td>';
164                if (count($items['en'][$ns]) == 0) {
165                    $R->doc .= '<td class="rightalign"> - </td>';
166                } else {
167                    $R->doc .= '<td class="rightalign">'.round(100*count($pages)/count($items['en'][$ns])).'%</td>';
168                }
169
170                $R->doc .= '<td>';
171                // outdated
172                $outdated = array();
173                foreach ($pages as $id => $date) {
174                    if ($date < $items['en'][$ns][$id]) {
175                        $outdated[] = $id;
176                    }
177                }
178                if (count($outdated) > 0) {
179                    $itr = 0;
180                    $R->doc .= '<b>Outdated:</b><br/>';
181                    do
182                        $R->doc .= '<a href="'.wl($lc.':'.$outdated[$itr]).'">'.$outdated[$itr].'</a> ';
183                    while (++$itr < $data['outdated'] && $itr < count($outdated));
184                    if ($itr < count($outdated)) {
185                        $R->doc .= 'and '.(count($outdated)-$itr).' more.';
186                    }
187                    $R->doc .= '<br/> ';
188                }
189                // rouge pages
190                if (count($items['en'][$ns]) > 0) {
191                    $extra_pages = array_keys(array_diff_key($pages,$items['en'][$ns]));
192                    $func = create_function('$id' , "return '<a href=\"'.wl('$lc:'.\$id).'\">'.\$id.'</a>';");
193                    if (count($extra_pages) > 0) {
194                        $extra_pages = array_map($func, $extra_pages);
195                        $R->doc .= '<b>Rogue pages:</b><br/> '.implode(', ', $extra_pages).'<br/>';
196                    }
197                }
198
199                $R->doc .= '</td>';
200                $R->doc .= '</tr>';
201                $pageTotal += count($pages);
202            }
203            $R->doc .= '<tr>';
204            $R->doc .= '<th>'.$lc.'</th>';
205            $R->doc .= '<th>Total</th>';
206            $R->doc .= '<th class="rightalign">'.$pageTotal.'</th>';
207            $R->doc .= '<th></th>';
208            $R->doc .= '<th></th>';
209            $R->doc .= '</tr>';
210        }
211        $R->doc .= '</table>';
212    }
213
214    function baseNS($id){
215        $pos = strpos((string)$id,':');
216        if($pos!==false){
217            return substr((string)$id,0,$pos);
218        }
219        return false;
220    }
221
222    function skipBaseNS($id){
223        $pos = strpos((string)$id,':');
224        if($pos!==false){
225            return substr((string)$id,$pos+1);
226        }
227        return false;
228    }
229}
230
231