xref: /plugin/approve/syntax/table.php (revision 99eaf3e2f5892270345af3865c95e0c55f3cc762)
1<?php
2
3use dokuwiki\plugin\approve\meta\ApproveConst;
4
5// must be run within DokuWiki
6if(!defined('DOKU_INC')) die();
7
8
9class syntax_plugin_approve_table extends DokuWiki_Syntax_Plugin {
10
11    protected $states = [];
12
13    public function __construct() {
14        $this->states = [$this->getConf('sum approved'),
15                         $this->getConf('sum ready for approval'),
16                         $this->getConf('sum draft')];
17    }
18
19    function getType() {
20        return 'substition';
21    }
22
23    function getSort() {
24        return 20;
25    }
26
27    function PType() {
28        return 'block';
29    }
30
31    function connectTo($mode) {
32        $this->Lexer->addSpecialPattern('----+ *approve table *-+\n.*?----+', $mode,'plugin_approve_table');
33    }
34
35    function handle($match, $state, $pos, Doku_Handler $handler){
36        $lines = explode("\n", $match);
37        array_shift($lines);
38        array_pop($lines);
39
40        $params = [];
41        foreach ($lines as $line) {
42            $pair = explode(':', $line, 2);
43            if (count($pair) < 2) {
44                continue;
45            }
46            $key = trim($pair[0]);
47            $value = trim($pair[1]);
48            if ($key == 'states') {
49                $value = array_map('trim', explode(',', $value));
50                //normalize
51                $value = array_map('strtolower', $value);
52                $value = array_map('ucfirst', $value);
53                foreach ($value as $state) {
54                    if (!in_array($state, $this->states)) {
55                        msg('approve plugin: unknown state "'.$state.'" should be: ' .
56                            implode(', ', $this->states), -1);
57                        return false;
58                    }
59                }
60            } elseif($key == 'filter' && preg_match($value, null) === false) {
61                msg('approve plugin: invalid filter regex', -1);
62                return false;
63            } elseif ($key == 'summarize') {
64                $value = $value == '0' ? false : true;
65            }
66            $params[$key] = $value;
67        }
68        return $params;
69    }
70
71    function render($mode, Doku_Renderer $renderer, $params) {
72        global $conf;
73
74        if ($mode != 'xhtml') return false;
75        if ($params === false) return false;
76
77        $defaults = [
78            'namespace' => '',
79            'filter' => false,
80            'states' => $this->states,
81            'summarize' => true,
82        ];
83
84        $params = array_replace($defaults, $params);
85
86        $namespace = cleanID(getNS($params['namespace'] . ":*"));
87
88        $pages = $this->_getPagesFromNamespace($namespace, $params['filter'], $params['states']);
89
90        usort($pages, array($this,'_pagesorter'));
91
92        // Output Table
93        $renderer->doc .= '<table><tr>';
94        $renderer->doc .= '<th>' . $this->getLang('hdr_page') . '</th>';
95        $renderer->doc .= '<th>' . $this->getLang('hdr_state') . '</th>';
96        $renderer->doc .= '<th>' . $this->getLang('hdr_updated') . '</th>';
97        $renderer->doc .= '</tr>';
98
99
100        $all_approved = 0;
101        $all_approved_ready = 0;
102        $all = 0;
103
104        $working_ns = null;
105        foreach($pages as $page) {
106            // $page: 0 -> pagename, 1 -> true -> approved else false, 2 -> last changed date
107            $this_ns = getNS($page[0]);
108
109            if($this_ns != $working_ns) {
110                $name_ns = $this_ns;
111                if($this_ns == '') { $name_ns = 'root'; }
112                $renderer->doc .= '<tr><td colspan="3"><a href="';
113                $renderer->doc .= wl($this_ns . ':' . $this->getConf('start'));
114                $renderer->doc .= '">';
115                $renderer->doc .= $name_ns;
116                $renderer->doc .= '</a> ';
117                $renderer->doc .= '</td></tr>';
118                $working_ns = $this_ns;
119            }
120
121            $updated = '<a href="' . wl($page[0]) . '">' . dformat($page[2]) . '</a>';
122
123            $class = 'plugin__approve_red';
124            $state = $this->getLang('draft');
125            $all += 1;
126
127            if ($page[1] === 'approved') {
128                $class = 'plugin__approve_green';
129                $state = $this->getLang('approved');
130                $all_approved += 1;
131            } elseif ($page[1] === 'ready for approval' && $this->getConf('ready_for_approval') === 1) {
132                $class = 'plugin__approve_ready';
133                $state = $this->getLang('marked_approve_ready');
134                $all_approved_ready += 1;
135            }
136
137            $renderer->doc .= '<tr class="'.$class.'">';
138            $renderer->doc .= '<td><a href="';
139            $renderer->doc .= wl($page[0]);
140            $renderer->doc .= '">';
141            if ($conf['useheading'] === '1') {
142                $heading = p_get_first_heading($page[0]);
143                if ($heading != '') {
144                    $renderer->doc .= $heading;
145                } else {
146                    $renderer->doc .= $page[0];
147                }
148
149            } else {
150                $renderer->doc .= $page[0];
151            }
152
153            $renderer->doc .= '</a></td><td>';
154            $renderer->doc .= '<strong>'.$state. '</strong> '. $this->getLang('by'). ' ' . $page[4];
155            $renderer->doc .= '</td><td>';
156            $renderer->doc .= $updated;
157            $renderer->doc .= '</td></tr>';
158        }
159
160        if ($params['summarize']) {
161            if($this->getConf('ready_for_approval') === 1) {
162                $renderer->doc .= '<tr><td><strong>';
163                $renderer->doc .= $this->getLang('all_approved_ready');
164                $renderer->doc .= '</strong></td>';
165
166                $renderer->doc .= '<td colspan="2">';
167                $percent       = 0;
168                if($all > 0) {
169                    $percent = $all_approved_ready * 100 / $all;
170                }
171                $renderer->doc .= $all_approved_ready . ' / ' . $all . sprintf(" (%.0f%%)", $percent);
172                $renderer->doc .= '</td></tr>';
173            }
174
175            $renderer->doc .= '<tr><td><strong>';
176            $renderer->doc .= $this->getLang('all_approved');
177            $renderer->doc .= '</strong></td>';
178
179            $renderer->doc .= '<td colspan="2">';
180            $percent       = 0;
181            if($all > 0) {
182                $percent = $all_approved * 100 / $all;
183            }
184            $renderer->doc .= $all_approved . ' / ' . $all . sprintf(" (%.0f%%)", $percent);
185            $renderer->doc .= '</td></tr>';
186        }
187
188        $renderer->doc .= '</table>';
189        return true;
190    }
191
192    function _search_helper(&$data, $base, $file, $type, $lvl, $opts) {
193        global $lang;
194
195        $ns = $opts[0];
196        $invalid_ns = $opts[1];
197        $page_regex = $opts[2];
198        $states = $opts[3];
199
200        if ($type == 'd') {
201            return true;
202        }
203
204        if (!preg_match('#\.txt$#', $file)) {
205            return false;
206        }
207
208        $id = pathID($ns . $file);
209        if (!empty($invalid_ns) && $this->hlp->in_namespace($invalid_ns, $id)) {
210            return false;
211        }
212
213        //check page_regex
214        if ($page_regex && !preg_match($page_regex, $id)) {
215            return false;
216        }
217
218        //check states
219
220
221        $meta = p_get_metadata($id);
222
223
224        //check states
225        $sum = $meta['last_change']['sum'];
226        if ($sum == $this->getConf('sum approved') && !in_array($this->getConf('sum approved'), $states)) {
227            return false;
228        }
229
230        if ($sum == $this->getConf('sum ready for approval') &&
231            !in_array($this->getConf('sum ready for approval'), $states)) {
232            return false;
233        }
234
235        if ($sum != $this->getConf('sum approved') &&
236            $sum != $this->getConf('sum ready for approval') &&
237            !in_array($this->getConf('sum draft'), $states)) {
238            return false;
239        }
240
241        $date = $meta['date']['modified'];
242        if (isset($meta['last_change']) && $meta['last_change']['sum'] === $this->getConf('sum approved')) {
243            $approved = 'approved';
244        } elseif (isset($meta['last_change']) && $meta['last_change']['sum'] === $this->getConf('sum ready for approval')) {
245            $approved = 'ready for approval';
246        } else {
247            $approved = 'not approved';
248        }
249
250        if (isset($meta['last_change'])) {
251            $user = $meta['last_change']['user'];
252
253            if (isset($meta['contributor'][$user])) {
254                $full_name = $meta['contributor'][$user];
255            } else {
256                $full_name = $meta['creator'];
257            }
258        } else {
259            $user = '';
260            $full_name = '('.$lang['external_edit'].')';
261        }
262
263
264        $data[] = array($id, $approved, $date, $user, $full_name);
265
266        return false;
267    }
268
269    function _getPagesFromNamespace($namespace, $page_regex=false, $states=[]) {
270        global $conf;
271        $dir = $conf['datadir'] . '/' . str_replace(':', '/', $namespace);
272        $pages = array();
273        search($pages, $dir, array($this,'_search_helper'),
274               array($namespace, $this->getConf('no_apr_namespaces'), $page_regex, $states));
275
276        return $pages;
277    }
278
279
280
281    /**
282     * Custom sort callback
283     */
284    function _pagesorter($a, $b){
285        $ac = explode(':',$a[0]);
286        $bc = explode(':',$b[0]);
287        $an = count($ac);
288        $bn = count($bc);
289
290        // Same number of elements, can just string sort
291        if($an == $bn) { return strcmp($a[0], $b[0]); }
292
293        // For each level:
294        // If this is not the last element in either list:
295        //   same -> continue
296        //   otherwise strcmp
297        // If this is the last element in either list, it wins
298        $n = 0;
299        while(true) {
300            if($n + 1 == $an) { return -1; }
301            if($n + 1 == $bn) { return 1; }
302            $s = strcmp($ac[$n], $bc[$n]);
303            if($s != 0) { return $s; }
304            $n += 1;
305        }
306    }
307
308}
309