1<?php
2/**
3 * DokuWiki Plugin groupmatrix (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Anna Dabrowska <dokuwiki@cosmocode.de>
7 */
8
9class syntax_plugin_groupmatrix_table extends DokuWiki_Syntax_Plugin
10{
11    const MARK = '✓';
12
13
14    /**
15     * @return string Syntax mode type
16     */
17    public function getType()
18    {
19        return 'substition';
20    }
21
22    /**
23     * @return string Paragraph type
24     */
25    public function getPType()
26    {
27        return 'block';
28    }
29
30    /**
31     * @return int Sort order - Low numbers go before high numbers
32     */
33    public function getSort()
34    {
35        return 100;
36    }
37
38    /**
39     * Connect lookup pattern to lexer.
40     *
41     * @param string $mode Parser mode
42     */
43    public function connectTo($mode)
44    {
45        $this->Lexer->addSpecialPattern('---+ *groupmatrix *-+\n.*?\n----+', $mode, 'plugin_groupmatrix_table');
46    }
47
48    /**
49     * Handle matches of the groupmatrix syntax
50     *
51     * @param string $match The match of the syntax
52     * @param int $state The state of the handler
53     * @param int $pos The position in the document
54     * @param Doku_Handler $handler The handler
55     *
56     * @return array Data for the renderer
57     */
58    public function handle($match, $state, $pos, Doku_Handler $handler)
59    {
60        $data = [
61            'headers' => [],
62            'rows' => [],
63        ];
64
65        $lines = explode("\n", $match);
66
67        // get rid of opening and closing syntax lines
68        array_shift($lines);
69        array_pop($lines);
70
71        $cfg = [];
72        foreach ($lines as $line) {
73            list($key, $value) = explode(':', $line);
74            $cfg[trim($key)] = trim($value);
75        }
76
77        if (empty($cfg['groups'])) {
78            msg('Missing groups configuration', -1);
79            return $data;
80        }
81
82        $data['attributes'] = $this->trimexplode(',', $cfg['attributes']);
83
84        // localize attribute names in table header
85        $data['attributeHeaders'] = array_map(function ($attr) {
86            return $this->getLang($attr) ?: $attr;
87        }, $data['attributes']);
88
89        $data['groups'] = $this->trimexplode(',', $cfg['groups']);
90        $titles = $this->trimexplode(',', $cfg['titles']);
91        if(empty($data['attributes'])) $data['attributes'] = ['user'];
92
93
94        $groupHeaders = $titles ? array_replace($data['groups'], $titles) : $data['groups'];
95        $data['headers'] = array_merge($data['attributeHeaders'], $groupHeaders);
96
97        return $data;
98    }
99
100    /**
101     * Render xhtml output
102     *
103     * @param string $mode Renderer mode (supported modes: xhtml)
104     * @param Doku_Renderer $renderer The renderer
105     * @param array $data The data from the handler() function
106     *
107     * @return bool If rendering was successful.
108     */
109    public function render($mode, Doku_Renderer $renderer, $data)
110    {
111        if ($mode !== 'xhtml') {
112            return false;
113        }
114
115        /** @var DokuWiki_Auth_Plugin $auth */
116        global $auth;
117
118        $groups = $data['groups'];
119        $attributes = $data['attributes'];
120
121        $users = $auth->retrieveUsers(0,
122            -1,
123            ['grps' => implode('|', $groups)]
124        );
125
126        // no results from auth backend
127        if (empty($users)) {
128            $rows = [];
129        } else {
130            // convert user data into matrix row: attributes and group membership flags
131            $rows = array_map(function ($user, $username) use ($groups, $attributes) {
132                // special handling of 'user': always use the wiki username from array key
133                $user['user'] = $username;
134
135                foreach ($attributes as $attribute) {
136                    $row[$attribute] = $user[$attribute] ?: '';
137                }
138                foreach ($groups as $group) {
139                    $row['memberof'][$group] = in_array($group, $user['grps']) ? self::MARK : '';
140                }
141
142                return $row;
143            }, $users, array_keys($users));
144        }
145
146        $renderer->doc .= $this->renderTable($data['headers'], $rows);
147        return true;
148    }
149
150    /**
151     * Return table HTML. The first column is the user name, the rest comes from config.
152     *
153     * @param array $headers
154     * @param array $rows
155     * @param string $className
156     * @return string
157     */
158    protected function renderTable($headers, $rows, $className = '')
159    {
160        $html = '<table class="inline ' . $className . '">';
161
162        $html .= '<thead>';
163        $html .= '<tr>';
164        foreach ($headers as $header) {
165            $html .= '<th>' . $header . '</th>';
166        }
167        $html .= '</tr>';
168        $html .= '</thead>';
169
170        $html .= '<tbody>';
171        if ($rows) {
172            foreach ($rows as $row) {
173                $html .= '<tr>';
174                $html .= $this->renderTableCells($row);
175                $html .= '</tr>';
176            }
177        }
178        $html .= '</tbody>';
179        $html .= '</table>';
180
181        return $html;
182    }
183
184    /**
185     * Explode a string and trim the resulting array items
186     *
187     * @param string $delimiter
188     * @param string $string
189     * @return array
190     */
191    protected function trimexplode($delimiter, $string)
192    {
193        $arr = [];
194        foreach (explode($delimiter, $string) as $value) {
195            if (!$value) {
196                continue;
197            }
198            $arr[] = trim($value);
199        }
200        return $arr;
201    }
202
203    /**
204     * Wrap all items in <td> tags, flattening any contained arrays.
205     *
206     * @param array $row
207     * @param string $html
208     * @return string
209     */
210    protected function renderTableCells($row, $html = '')
211    {
212        foreach ($row as $item) {
213            if (!is_array($item)) {
214                if ($item === self::MARK) {
215                    $html .= '<td class="centeralign">';
216                } else {
217                    $html .= '<td>';
218                }
219                $html .= hsc($item) . '</td>';
220            } else {
221                return $this->renderTableCells($item, $html);
222            }
223        }
224        return $html;
225    }
226}
227
228