1<?php 2/** 3 * 4 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 5 * @author Andreas Gohr <andi@splitbrain.org> 6 */ 7// must be run within Dokuwiki 8if(!defined('DOKU_INC')) die(); 9 10/** 11 * Class syntax_plugin_dataau_entry 12 */ 13class syntax_plugin_dataau_entry extends DokuWiki_Syntax_Plugin { 14 15 /** 16 * @var helper_plugin_dataau will hold the dataau helper plugin 17 */ 18 var $dthlp = null; 19 20 /** 21 * Constructor. Load helper plugin 22 */ 23 function __construct() { 24 $this->dthlp = plugin_load('helper', 'dataau'); 25 if(!$this->dthlp) msg('Loading the dataau helper failed. Make sure the dataau plugin is installed.', -1); 26 } 27 28 /** 29 * What kind of syntax are we? 30 */ 31 function getType() { 32 return 'substition'; 33 } 34 35 /** 36 * What about paragraphs? 37 */ 38 function getPType() { 39 return 'block'; 40 } 41 42 /** 43 * Where to sort in? 44 */ 45 function getSort() { 46 return 155; 47 } 48 49 /** 50 * Connect pattern to lexer 51 */ 52 function connectTo($mode) { 53 $this->Lexer->addSpecialPattern('----+ *dataentry(?: [ a-zA-Z0-9_]*)?-+\n.*?\n----+', $mode, 'plugin_dataau_entry'); 54 } 55 56 /** 57 * Handle the match - parse the data 58 * 59 * @param string $match The text matched by the patterns 60 * @param int $state The lexer state for the match 61 * @param int $pos The character position of the matched text 62 * @param Doku_Handler $handler The Doku_Handler object 63 * @return bool|array Return an array with all data you want to use in render, false don't add an instruction 64 */ 65 function handle($match, $state, $pos, Doku_Handler $handler) { 66 if(!$this->dthlp->ready()) return null; 67 68 // get lines 69 $lines = explode("\n", $match); 70 array_pop($lines); 71 $class = array_shift($lines); 72 $class = str_replace('dataentry', '', $class); 73 $class = trim($class, '- '); 74 75 // parse info 76 $dataau = array(); 77 $columns = array(); 78 foreach($lines as $line) { 79 // ignore comments 80 preg_match('/^(.*?(?<![&\\\\]))(?:#(.*))?$/', $line, $matches); 81 $line = $matches[1]; 82 $line = str_replace('\\#', '#', $line); 83 $line = trim($line); 84 if(empty($line)) continue; 85 $line = preg_split('/\s*:\s*/', $line, 2); 86 87 $column = $this->dthlp->_column($line[0]); 88 if(isset($matches[2])) { 89 $column['comment'] = $matches[2]; 90 } 91 if($column['multi']) { 92 if(!isset($dataau[$column['key']])) { 93 // init with empty array 94 // Note that multiple occurrences of the field are 95 // practically merged 96 $dataau[$column['key']] = array(); 97 } 98 $vals = explode(',', $line[1]); 99 foreach($vals as $val) { 100 $val = trim($this->dthlp->_cleanData($val, $column['type'])); 101 if($val == '') continue; 102 if(!in_array($val, $dataau[$column['key']])) { 103 $dataau[$column['key']][] = $val; 104 } 105 } 106 } else { 107 $dataau[$column['key']] = $this->dthlp->_cleanData($line[1], $column['type']); 108 } 109 $columns[$column['key']] = $column; 110 } 111 return array( 112 'dataau' => $dataau, 'cols' => $columns, 'classes' => $class, 113 'pos' => $pos, 'len' => strlen($match) 114 ); // not utf8_strlen 115 } 116 117 /** 118 * Create output or save the data 119 * 120 * @param $format string output format being rendered 121 * @param $renderer Doku_Renderer the current renderer object 122 * @param $dataau array data created by handler() 123 * @return boolean rendered correctly? 124 */ 125 function render($format, Doku_Renderer $renderer, $dataau) { 126 if(is_null($dataau)) return false; 127 if(!$this->dthlp->ready()) return false; 128 129 global $ID; 130 switch($format) { 131 case 'xhtml': 132 /** @var $renderer Doku_Renderer_xhtml */ 133 $this->_showData($dataau, $renderer); 134 return true; 135 case 'metadata': 136 /** @var $renderer Doku_Renderer_metadata */ 137 $this->_saveData($dataau, $ID, $renderer->meta['title']); 138 return true; 139 case 'plugin_dataau_edit': 140 /** @var $renderer Doku_Renderer_plugin_dataau_edit */ 141 $this->_editData($dataau, $renderer); 142 return true; 143 default: 144 return false; 145 } 146 } 147 148 /** 149 * Output the data in a table 150 * 151 * @param array $data 152 * @param Doku_Renderer_xhtml $R 153 */ 154 function _showData($dataau, $R) { 155 global $ID; 156 $ret = ''; 157 158 $sectionEditData = ['target' => 'plugin_dataau']; 159 if (!defined('SEC_EDIT_PATTERN')) { 160 // backwards-compatibility for Frusterick Manners (2017-02-19) 161 $sectionEditData = 'plugin_dataau'; 162 } 163 $dataau['classes'] .= ' ' . $R->startSectionEdit($dataau['pos'], $sectionEditData); 164 165 $ret .= '<div class="inline dataauplugin_entry ' . $dataau['classes'] . '"><dl>'; 166 $class_names = array(); 167 foreach($dataau['dataau'] as $key => $val) { 168 if($val == '' || !count($val)) continue; 169 $type = $dataau['cols'][$key]['type']; 170 if(is_array($type)) { 171 $type = $type['type']; 172 } 173 if($type === 'hidden') continue; 174 175 $class_name = hsc(sectionID($key, $class_names)); 176 $ret .= '<dt class="' . $class_name . '">' . hsc($dataau['cols'][$key]['title']) . '<span class="sep">: </span></dt>'; 177 $ret .= '<dd class="' . $class_name . '">'; 178 if(is_array($val)) { 179 $cnt = count($val); 180 for($i = 0; $i < $cnt; $i++) { 181 switch($type) { 182 case 'wiki': 183 $val[$i] = $ID . '|' . $val[$i]; 184 break; 185 } 186 $ret .= $this->dthlp->_formatData($dataau['cols'][$key], $val[$i], $R); 187 if($i < $cnt - 1) { 188 $ret .= '<span class="sep">, </span>'; 189 } 190 } 191 } else { 192 switch($type) { 193 case 'wiki': 194 $val = $ID . '|' . $val; 195 break; 196 } 197 $ret .= $this->dthlp->_formatData($dataau['cols'][$key], $val, $R); 198 } 199 $ret .= '</dd>'; 200 } 201 $ret .= '</dl></div>'; 202 $R->doc .= $ret; 203 $R->finishSectionEdit($dataau['len'] + $dataau['pos']); 204 } 205 206 /** 207 * Save date to the database 208 */ 209 function _saveData($dataau, $id, $title) { 210 $sqlite = $this->dthlp->_getDB(); 211 if(!$sqlite) return false; 212 213 if(!$title) { 214 $title = $id; 215 } 216 217 $class = $dataau['classes']; 218 219 // begin transaction 220 $sqlite->query("BEGIN TRANSACTION"); 221 222 // store page info 223 $this->replaceQuery( 224 "INSERT OR IGNORE INTO pages (page,title,class) VALUES (?,?,?)", 225 $id, $title, $class 226 ); 227 228 // Update title if insert failed (record already saved before) 229 $revision = filemtime(wikiFN($id)); 230 $this->replaceQuery( 231 "UPDATE pages SET title = ?, class = ?, lastmod = ? WHERE page = ?", 232 $title, $class, $revision, $id 233 ); 234 235 // fetch page id 236 $res = $this->replaceQuery("SELECT pid FROM pages WHERE page = ?", $id); 237 $pid = (int) $sqlite->res2single($res); 238 $sqlite->res_close($res); 239 240 if(!$pid) { 241 msg("dataau plugin: failed saving data", -1); 242 $sqlite->query("ROLLBACK TRANSACTION"); 243 return false; 244 } 245 246 // remove old data 247 $sqlite->query("DELETE FROM DATA WHERE pid = ?", $pid); 248 249 // insert new data 250 foreach($dataau['dataau'] as $key => $val) { 251 if(is_array($val)) foreach($val as $v) { 252 $this->replaceQuery( 253 "INSERT INTO DATA (pid, KEY, VALUE) VALUES (?, ?, ?)", 254 $pid, $key, $v 255 ); 256 } else { 257 $this->replaceQuery( 258 "INSERT INTO DATA (pid, KEY, VALUE) VALUES (?, ?, ?)", 259 $pid, $key, $val 260 ); 261 } 262 } 263 264 // finish transaction 265 $sqlite->query("COMMIT TRANSACTION"); 266 267 return true; 268 } 269 270 /** 271 * @return bool|mixed 272 */ 273 function replaceQuery() { 274 $args = func_get_args(); 275 $argc = func_num_args(); 276 277 if($argc > 1) { 278 for($i = 1; $i < $argc; $i++) { 279 $dataau = array(); 280 $dataau['sql'] = $args[$i]; 281 $this->dthlp->_replacePlaceholdersInSQL($dataau); 282 $args[$i] = $dataau['sql']; 283 } 284 } 285 286 $sqlite = $this->dthlp->_getDB(); 287 if(!$sqlite) return false; 288 289 return call_user_func_array(array(&$sqlite, 'query'), $args); 290 } 291 292 /** 293 * The custom editor for editing data entries 294 * 295 * Gets called from action_plugin_dataau::_editform() where also the form member is attached 296 * 297 * @param array $data 298 * @param Doku_Renderer_plugin_dataau_edit $renderer 299 */ 300 function _editData($dataau, &$renderer) { 301 $renderer->form->startFieldset($this->getLang('dataentry')); 302 $renderer->form->_content[count($renderer->form->_content) - 1]['class'] = 'plugin__dataau'; 303 $renderer->form->addHidden('range', '0-0'); // Adora Belle bugfix 304 305 if($this->getConf('edit_content_only')) { 306 $renderer->form->addHidden('dataau_edit[classes]', $dataau['classes']); 307 308 $columns = array('title', 'value', 'comment'); 309 $class = 'edit_content_only'; 310 311 } else { 312 $renderer->form->addElement(form_makeField('text', 'dataau_edit[classes]', $dataau['classes'], $this->getLang('class'), 'dataau__classes')); 313 314 $columns = array('title', 'type', 'multi', 'value', 'comment'); 315 $class = 'edit_all_content'; 316 317 // New line 318 $dataau['dataau'][''] = ''; 319 $dataau['cols'][''] = array('type' => '', 'multi' => false); 320 } 321 322 $renderer->form->addElement("<table class=\"$class\">"); 323 324 //header 325 $header = '<tr>'; 326 foreach($columns as $column) { 327 $header .= '<th class="' . $column . '">' . $this->getLang($column) . '</th>'; 328 } 329 $header .= '</tr>'; 330 $renderer->form->addElement($header); 331 332 //rows 333 $n = 0; 334 foreach($dataau['cols'] as $key => $vals) { 335 $fieldid = 'dataau_edit[dataau][' . $n++ . ']'; 336 $content = $vals['multi'] ? implode(', ', $dataau['dataau'][$key]) : $dataau['dataau'][$key]; 337 if(is_array($vals['type'])) { 338 $vals['basetype'] = $vals['type']['type']; 339 if(isset($vals['type']['enum'])) { 340 $vals['enum'] = $vals['type']['enum']; 341 } 342 $vals['type'] = $vals['origtype']; 343 } else { 344 $vals['basetype'] = $vals['type']; 345 } 346 347 if($vals['type'] === 'hidden') { 348 $renderer->form->addElement('<tr class="hidden">'); 349 } else { 350 $renderer->form->addElement('<tr>'); 351 } 352 if($this->getConf('edit_content_only')) { 353 if(isset($vals['enum'])) { 354 $values = preg_split('/\s*,\s*/', $vals['enum']); 355 if(!$vals['multi']) { 356 array_unshift($values, ''); 357 } 358 $content = form_makeListboxField( 359 $fieldid . '[value][]', 360 $values, 361 $dataau['dataau'][$key], 362 $vals['title'], 363 '', '', 364 ($vals['multi'] ? array('multiple' => 'multiple') : array()) 365 ); 366 } else { 367 $classes = 'dataau_type_' . $vals['type'] . ($vals['multi'] ? 's' : '') . ' ' 368 . 'dataau_type_' . $vals['basetype'] . ($vals['multi'] ? 's' : ''); 369 370 $attr = array(); 371 if($vals['basetype'] == 'date' && !$vals['multi']) { 372 $attr['class'] = 'datepicker'; 373 } 374 375 $content = form_makeField('text', $fieldid . '[value]', $content, $vals['title'], '', $classes, $attr); 376 377 } 378 $cells = array( 379 hsc($vals['title']) . ':', 380 $content, 381 '<span title="' . hsc($vals['comment']) . '">' . hsc($vals['comment']) . '</span>' 382 ); 383 foreach(array('multi', 'comment', 'type') as $field) { 384 $renderer->form->addHidden($fieldid . "[$field]", $vals[$field]); 385 } 386 $renderer->form->addHidden($fieldid . "[title]", $vals['origkey']); //keep key as key, even if title is translated 387 } else { 388 $check_dataau = $vals['multi'] ? array('checked' => 'checked') : array(); 389 $cells = array( 390 form_makeField('text', $fieldid . '[title]', $vals['origkey'], $this->getLang('title')), // when editable, always use the pure key, not a title 391 form_makeMenuField( 392 $fieldid . '[type]', 393 array_merge( 394 array( 395 '', 'page', 'nspage', 'title', 396 'img', 'mail', 'url', 'tag', 'wiki', 'dt', 'hidden' 397 ), 398 array_keys($this->dthlp->_aliases()) 399 ), 400 $vals['type'], 401 $this->getLang('type') 402 ), 403 form_makeCheckboxField($fieldid . '[multi]', array('1', ''), $this->getLang('multi'), '', '', $check_dataau), 404 form_makeField('text', $fieldid . '[value]', $content, $this->getLang('value')), 405 form_makeField('text', $fieldid . '[comment]', $vals['comment'], $this->getLang('comment'), '', 'dataau_comment', array('readonly' => 1, 'title' => $vals['comment'])) 406 ); 407 } 408 409 foreach($cells as $index => $cell) { 410 $renderer->form->addElement("<td class=\"{$columns[$index]}\">"); 411 $renderer->form->addElement($cell); 412 $renderer->form->addElement('</td>'); 413 } 414 $renderer->form->addElement('</tr>'); 415 } 416 417 $renderer->form->addElement('</table>'); 418 $renderer->form->endFieldset(); 419 } 420 421 /** 422 * Escapes the given value against being handled as comment 423 * 424 * @todo bad naming 425 * @param $txt 426 * @return mixed 427 */ 428 public static function _normalize($txt) { 429 return str_replace('#', '\#', trim($txt)); 430 } 431 432 /** 433 * Handles the data posted from the editor to recreate the entry syntax 434 * 435 * @param array $dataau data given via POST 436 * @return string 437 */ 438 public static function editToWiki($dataau) { 439 $nudataau = array(); 440 441 $len = 0; // we check the maximum lenght for nice alignment later 442 foreach($dataau['dataau'] as $field) { 443 if(is_array($field['value'])) { 444 $field['value'] = join(', ', $field['value']); 445 } 446 $field = array_map('trim', $field); 447 if($field['title'] === '') continue; 448 449 $name = syntax_plugin_dataau_entry::_normalize($field['title']); 450 451 if($field['type'] !== '') { 452 $name .= '_' . syntax_plugin_dataau_entry::_normalize($field['type']); 453 } elseif(substr($name, -1, 1) === 's') { 454 $name .= '_'; // when the field name ends in 's' we need to secure it against being assumed as multi 455 } 456 // 's' is added to either type or name for multi 457 if($field['multi'] === '1') { 458 $name .= 's'; 459 } 460 461 $nudataau[] = array($name, syntax_plugin_dataau_entry::_normalize($field['value']), $field['comment']); 462 $len = max($len, utf8_strlen($nudataau[count($nudataau) - 1][0])); 463 } 464 465 $ret = '---- dataentry ' . trim($dataau['classes']) . ' ----' . DOKU_LF; 466 foreach($nudataau as $field) { 467 $ret .= $field[0] . str_repeat(' ', $len + 1 - utf8_strlen($field[0])) . ': '; 468 $ret .= $field[1]; 469 if($field[2] !== '') { 470 $ret .= ' # ' . $field[2]; 471 } 472 $ret .= DOKU_LF; 473 } 474 $ret .= "----\n"; 475 return $ret; 476 } 477} 478