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