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