15153720fSfkaag71<?php 25153720fSfkaag71/** 35153720fSfkaag71 * Strata, data entry plugin 45153720fSfkaag71 * 55153720fSfkaag71 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 65153720fSfkaag71 * @author Brend Wanders <b.wanders@utwente.nl> 75153720fSfkaag71 */ 85153720fSfkaag71 95153720fSfkaag71if (!defined('DOKU_INC')) die('Meh.'); 10*acfca0a3SFKaaguse dokuwiki\Utf8\Clean; 115153720fSfkaag71 125153720fSfkaag71/** 135153720fSfkaag71 * Data entry syntax for dedicated data blocks. 145153720fSfkaag71 */ 155153720fSfkaag71class syntax_plugin_strata_entry extends DokuWiki_Syntax_Plugin { 165153720fSfkaag71 protected static $previewMetadata = array(); 175153720fSfkaag71 185153720fSfkaag71 function __construct() { 195153720fSfkaag71 $this->syntax =& plugin_load('helper', 'strata_syntax'); 205153720fSfkaag71 $this->util =& plugin_load('helper', 'strata_util'); 215153720fSfkaag71 $this->triples =& plugin_load('helper', 'strata_triples'); 225153720fSfkaag71 } 235153720fSfkaag71 245153720fSfkaag71 function getType() { 255153720fSfkaag71 return 'substition'; 265153720fSfkaag71 } 275153720fSfkaag71 285153720fSfkaag71 function getPType() { 295153720fSfkaag71 return 'block'; 305153720fSfkaag71 } 315153720fSfkaag71 325153720fSfkaag71 function getSort() { 335153720fSfkaag71 return 450; 345153720fSfkaag71 } 355153720fSfkaag71 365153720fSfkaag71 function connectTo($mode) { 375153720fSfkaag71 if($this->getConf('enable_entry')) { 385153720fSfkaag71 $this->Lexer->addSpecialPattern('<data(?: +[^#>]+?)?(?: *#[^>]*?)?>\s*?\n(?:.*?\n)*?\s*?</data>',$mode, 'plugin_strata_entry'); 395153720fSfkaag71 } 405153720fSfkaag71 } 415153720fSfkaag71 425153720fSfkaag71 function handle($match, $state, $pos, Doku_Handler $handler) { 435153720fSfkaag71 $result = array( 445153720fSfkaag71 'entry'=>'', 455153720fSfkaag71 'data'=> array( 465153720fSfkaag71 $this->util->getIsaKey(false) => array(), 475153720fSfkaag71 $this->util->getTitleKey(false) => array() 485153720fSfkaag71 ) 495153720fSfkaag71 ); 505153720fSfkaag71 515153720fSfkaag71 // allow for preprocessing by a subclass 525153720fSfkaag71 $match = $this->preprocess($match, $state, $pos, $handler, $result); 535153720fSfkaag71 545153720fSfkaag71 $lines = explode("\n",$match); 555153720fSfkaag71 $header = trim(array_shift($lines)); 565153720fSfkaag71 $footer = trim(array_pop($lines)); 575153720fSfkaag71 585153720fSfkaag71 595153720fSfkaag71 // allow subclasses to mangle header 605153720fSfkaag71 $header = $this->handleHeader($header, $result); 615153720fSfkaag71 625153720fSfkaag71 // extract header, and match it to get classes and fragment 635153720fSfkaag71 preg_match('/^( +[^#>]+)?(?: *#([^>]*?))?$/', $header, $header); 645153720fSfkaag71 655153720fSfkaag71 // process the classes into triples 660847ebd2SFKaag if (isset($header[1])) 670847ebd2SFKaag { 685153720fSfkaag71 foreach(preg_split('/\s+/',trim($header[1])) as $class) { 695153720fSfkaag71 if($class == '') continue; 705153720fSfkaag71 $result['data'][$this->util->getIsaKey(false)][] = array('value'=>$class,'type'=>'text', 'hint'=>null); 715153720fSfkaag71 } 720847ebd2SFKaag } 735153720fSfkaag71 745153720fSfkaag71 // process the fragment if necessary 750847ebd2SFKaag $result['entry'] = $header[2]??null; 765153720fSfkaag71 $result['position'] = $pos; 775153720fSfkaag71 if($result['entry'] != '') { 785153720fSfkaag71 $result['title candidate'] = array('value'=>$result['entry'], 'type'=>'text', 'hint'=>null); 795153720fSfkaag71 } 805153720fSfkaag71 815153720fSfkaag71 // parse tree 825153720fSfkaag71 $tree = $this->syntax->constructTree($lines,'data entry'); 835153720fSfkaag71 845153720fSfkaag71 // allow subclasses first pick in the tree 855153720fSfkaag71 $this->handleBody($tree, $result); 865153720fSfkaag71 875153720fSfkaag71 // fetch all lines 885153720fSfkaag71 $lines = $this->syntax->extractText($tree); 895153720fSfkaag71 905153720fSfkaag71 // sanity check 915153720fSfkaag71 if(count($tree['cs'])) { 925153720fSfkaag71 msg(sprintf($this->syntax->getLang('error_entry_block'), ($tree['cs'][0]['tag']?sprintf($this->syntax->getLang('named_group'),utf8_tohtml(hsc($tree['cs'][0]['tag']))):$this->syntax->getLang('unnamed_group')), utf8_tohtml(hsc($result['entry']))),-1); 935153720fSfkaag71 return array(); 945153720fSfkaag71 } 955153720fSfkaag71 965153720fSfkaag71 $p = $this->syntax->getPatterns(); 975153720fSfkaag71 985153720fSfkaag71 // now handle all lines 995153720fSfkaag71 foreach($lines as $line) { 1005153720fSfkaag71 $line = $line['text']; 1015153720fSfkaag71 // match a "property_type(hint)*: value" pattern 1025153720fSfkaag71 // (the * is only used to indicate that the value is actually a comma-seperated list) 1035153720fSfkaag71 // [grammar] ENTRY := PREDICATE TYPE? '*'? ':' ANY 1045153720fSfkaag71 if(preg_match("/^({$p->predicate})\s*({$p->type})?\s*(\*)?\s*:\s*({$p->any}?)$/",$line,$parts)) { 1055153720fSfkaag71 // assign useful names 1065153720fSfkaag71 list(, $property, $ptype, $multi, $values) = $parts; 1075153720fSfkaag71 list($type,$hint) = $p->type($ptype); 1085153720fSfkaag71 1095153720fSfkaag71 // trim property so we don't get accidental 'name ' keys 1105153720fSfkaag71 $property = utf8_trim($property); 1115153720fSfkaag71 1125153720fSfkaag71 // lazy create key bucket 1135153720fSfkaag71 if(!isset($result['data'][$property])) { 1145153720fSfkaag71 $result['data'][$property] = array(); 1155153720fSfkaag71 } 1165153720fSfkaag71 1175153720fSfkaag71 // determine values, splitting on commas if necessary 1185153720fSfkaag71 $values = ($multi == '*') ? explode(',',$values) : array($values); 1195153720fSfkaag71 1205153720fSfkaag71 // generate triples from the values 1215153720fSfkaag71 foreach($values as $v) { 1225153720fSfkaag71 $v = utf8_trim($v); 1235153720fSfkaag71 if($v == '') continue; 1245153720fSfkaag71 // replace the [[]] quasi-magic token with the empty string 1255153720fSfkaag71 if($v == '[[]]') $v = ''; 1265153720fSfkaag71 if(!isset($type) || $type == '') { 1275153720fSfkaag71 list($type, $hint) = $this->util->getDefaultType(); 1285153720fSfkaag71 } 1295153720fSfkaag71 $result['data'][$property][] = array('value'=>$v,'type'=>$type,'hint'=>($hint?:null)); 1305153720fSfkaag71 } 1315153720fSfkaag71 } else { 1325153720fSfkaag71 msg(sprintf($this->syntax->getLang('error_entry_line'), utf8_tohtml(hsc($line))),-1); 1335153720fSfkaag71 } 1345153720fSfkaag71 } 1355153720fSfkaag71 1365153720fSfkaag71 // normalize data: 1375153720fSfkaag71 // - Normalize all values 1385153720fSfkaag71 $buckets = $result['data']; 1395153720fSfkaag71 $result['data'] = array(); 1405153720fSfkaag71 1415153720fSfkaag71 foreach($buckets as $property=>&$bucket) { 1425153720fSfkaag71 // normalize the predicate 1435153720fSfkaag71 $property = $this->util->normalizePredicate($property); 1445153720fSfkaag71 1455153720fSfkaag71 // process all triples 1465153720fSfkaag71 foreach($bucket as &$triple) { 1475153720fSfkaag71 // normalize the value 1485153720fSfkaag71 $type = $this->util->loadType($triple['type']); 1495153720fSfkaag71 $triple['value'] = $type->normalize($triple['value'], $triple['hint']); 1505153720fSfkaag71 1515153720fSfkaag71 // lazy create property bucket 1525153720fSfkaag71 if(!isset($result['data'][$property])) { 1535153720fSfkaag71 $result['data'][$property] = array(); 1545153720fSfkaag71 } 1555153720fSfkaag71 1565153720fSfkaag71 $result['data'][$property][] = $triple; 1575153720fSfkaag71 } 1585153720fSfkaag71 } 1595153720fSfkaag71 1605153720fSfkaag71 1615153720fSfkaag71 // normalize title candidate 1625153720fSfkaag71 if(!empty($result['title candidate'])) { 1635153720fSfkaag71 $type = $this->util->loadType($result['title candidate']['type']); 1645153720fSfkaag71 $result['title candidate']['value'] = $type->normalize($result['title candidate']['value'], $result['title candidate']['hint']); 1655153720fSfkaag71 } 1665153720fSfkaag71 1675153720fSfkaag71 $footer = $this->handleFooter($footer, $result); 1685153720fSfkaag71 1695153720fSfkaag71 return $result; 1705153720fSfkaag71 } 1715153720fSfkaag71 1725153720fSfkaag71 /** 1735153720fSfkaag71 * Handles the whole match. This method is called before any processing 1745153720fSfkaag71 * is done by the actual class. 1755153720fSfkaag71 * 1765153720fSfkaag71 * @param match string the complete match 1775153720fSfkaag71 * @param state the parser state 1785153720fSfkaag71 * @param pos the position in the source 1795153720fSfkaag71 * @param the handler object 1805153720fSfkaag71 * @param result array the result array passed to the render method 1815153720fSfkaag71 * @return a preprocessed string 1825153720fSfkaag71 */ 1835153720fSfkaag71 function preprocess($match, $state, $pos, &$handler, &$result) { 1845153720fSfkaag71 return $match; 1855153720fSfkaag71 } 1865153720fSfkaag71 1875153720fSfkaag71 /** 1885153720fSfkaag71 * Handles the header of the syntax. This method is called before 1895153720fSfkaag71 * the header is handled. 1905153720fSfkaag71 * 1915153720fSfkaag71 * @param header string the complete header 1925153720fSfkaag71 * @param result array the result array passed to the render method 1935153720fSfkaag71 * @return a string containing the unhandled parts of the header 1945153720fSfkaag71 */ 1955153720fSfkaag71 function handleHeader($header, &$result) { 1965153720fSfkaag71 // remove prefix and suffix 1975153720fSfkaag71 return preg_replace('/(^<data)|( *>$)/','',$header); 1985153720fSfkaag71 } 1995153720fSfkaag71 2005153720fSfkaag71 /** 2015153720fSfkaag71 * Handles the body of the syntax. This method is called before any 2025153720fSfkaag71 * of the body is handled. 2035153720fSfkaag71 * 2045153720fSfkaag71 * @param tree array the parsed tree 2055153720fSfkaag71 * @param result array the result array passed to the render method 2065153720fSfkaag71 */ 2075153720fSfkaag71 function handleBody(&$tree, &$result) { 2085153720fSfkaag71 } 2095153720fSfkaag71 2105153720fSfkaag71 /** 2115153720fSfkaag71 * Handles the footer of the syntax. This method is called after the 2125153720fSfkaag71 * data has been parsed and normalized. 2135153720fSfkaag71 * 2145153720fSfkaag71 * @param footer string the footer string 2155153720fSfkaag71 * @param result array the result array passed to the render method 2165153720fSfkaag71 * @return a string containing the unhandled parts of the footer 2175153720fSfkaag71 */ 2185153720fSfkaag71 function handleFooter($footer, &$result) { 2195153720fSfkaag71 return ''; 2205153720fSfkaag71 } 2215153720fSfkaag71 2225153720fSfkaag71 2235153720fSfkaag71 protected function getPositions($data) { 2245153720fSfkaag71 global $ID; 2255153720fSfkaag71 2265153720fSfkaag71 // determine positions of other data entries 2275153720fSfkaag71 // (self::$previewMetadata is only filled if a preview_metadata was run) 2285153720fSfkaag71 if(isset(self::$previewMetadata[$ID])) { 2295153720fSfkaag71 $positions = self::$previewMetadata[$ID]['strata']['positions']; 2305153720fSfkaag71 } else { 2315153720fSfkaag71 $positions = p_get_metadata($ID, 'strata positions'); 2325153720fSfkaag71 } 2335153720fSfkaag71 2345153720fSfkaag71 // only read positions if we have them 2355153720fSfkaag71 if(is_array($positions) && isset($positions[$data['entry']])) { 2365153720fSfkaag71 $positions = $positions[$data['entry']]; 2375153720fSfkaag71 $currentPosition = array_search($data['position'],$positions); 2385153720fSfkaag71 $previousPosition = isset($positions[$currentPosition-1])?'data_fragment_'.$positions[$currentPosition-1]:null; 2395153720fSfkaag71 $nextPosition = isset($positions[$currentPosition+1])?'data_fragment_'.$positions[$currentPosition+1]:null; 2405153720fSfkaag71 $currentPosition = 'data_fragment_'.$positions[$currentPosition]; 2415153720fSfkaag71 } 2425153720fSfkaag71 2435153720fSfkaag71 return array($currentPosition, $previousPosition, $nextPosition); 2445153720fSfkaag71 } 2455153720fSfkaag71 2465153720fSfkaag71 function render($mode, Doku_Renderer $R, $data) { 2475153720fSfkaag71 global $ID; 2485153720fSfkaag71 2495153720fSfkaag71 if($data == array()) { 2505153720fSfkaag71 return false; 2515153720fSfkaag71 } 2525153720fSfkaag71 2535153720fSfkaag71 if($mode == 'xhtml' || $mode == 'odt') { 2545153720fSfkaag71 2555153720fSfkaag71 // determine actual header text 2565153720fSfkaag71 $heading = ''; 2575153720fSfkaag71 if(isset($data['data'][$this->util->getTitleKey()])) { 2585153720fSfkaag71 // use title triple if possible 2595153720fSfkaag71 $heading = $data['data'][$this->util->getTitleKey()][0]['value']; 2605153720fSfkaag71 } elseif (!empty($data['title candidate'])) { 2615153720fSfkaag71 // use title candidate if possible 2625153720fSfkaag71 $heading = $data['title candidate']['value']; 2635153720fSfkaag71 } else { 2645153720fSfkaag71 if(useHeading('content')) { 2655153720fSfkaag71 // fall back to page title, depending on wiki configuration 2665153720fSfkaag71 $heading = p_get_first_heading($ID); 2675153720fSfkaag71 } 2685153720fSfkaag71 2695153720fSfkaag71 if(!$heading) { 2705153720fSfkaag71 // use page id if all else fails 2715153720fSfkaag71 $heading = noNS($ID); 2725153720fSfkaag71 } 2735153720fSfkaag71 } 274*acfca0a3SFKaag 275*acfca0a3SFKaag list($currentPosition, $previousPosition, $nextPosition) = $this->getPositions($data); 276*acfca0a3SFKaag // render table header 277*acfca0a3SFKaag if($mode == 'xhtml') { $R->doc .= '<div class="strata-entry" id="'.Clean::deaccent(strtolower($heading)).'">'; } 278*acfca0a3SFKaag if($mode == 'odt' && isset($currentPosition) && method_exists ($R, 'insertBookmark')) { 279*acfca0a3SFKaag $R->insertBookmark($currentPosition, false); 280*acfca0a3SFKaag } 281*acfca0a3SFKaag $R->table_open(); 282*acfca0a3SFKaag $R->tablerow_open(); 283*acfca0a3SFKaag $R->tableheader_open(2); 284*acfca0a3SFKaag 2855153720fSfkaag71 $R->cdata($heading); 2865153720fSfkaag71 2875153720fSfkaag71 // display a comma-separated list of classes if the entry has classes 2885153720fSfkaag71 if(isset($data['data'][$this->util->getIsaKey()])) { 2895153720fSfkaag71 $R->emphasis_open(); 2905153720fSfkaag71 $R->cdata(' ('); 2915153720fSfkaag71 $values = $data['data'][$this->util->getIsaKey()]; 2925153720fSfkaag71 $this->util->openField($mode, $R, $this->util->getIsaKey()); 2935153720fSfkaag71 for($i=0;$i<count($values);$i++) { 2945153720fSfkaag71 $triple =& $values[$i]; 2955153720fSfkaag71 if($i!=0) $R->cdata(', '); 2965153720fSfkaag71 $type = $this->util->loadType($triple['type']); 2975153720fSfkaag71 $this->util->renderValue($mode, $R, $this->triples, $triple['value'], $triple['type'], $type, $triple['hint']); 2985153720fSfkaag71 } 2995153720fSfkaag71 $this->util->closeField($mode, $R); 3005153720fSfkaag71 $R->cdata(')'); 3015153720fSfkaag71 $R->emphasis_close(); 3025153720fSfkaag71 } 3035153720fSfkaag71 $R->tableheader_close(); 3045153720fSfkaag71 $R->tablerow_close(); 3055153720fSfkaag71 3065153720fSfkaag71 // render a row for each key, displaying the values as comma-separated list 3075153720fSfkaag71 foreach($data['data'] as $key=>$values) { 3085153720fSfkaag71 // skip isa and title keys 3095153720fSfkaag71 if($key == $this->util->getTitleKey() || $key == $this->util->getIsaKey()) continue; 3105153720fSfkaag71 3115153720fSfkaag71 // render row header 3125153720fSfkaag71 $R->tablerow_open(); 3135153720fSfkaag71 $R->tableheader_open(); 3145153720fSfkaag71 $this->util->renderPredicate($mode, $R, $this->triples, $key); 3155153720fSfkaag71 $R->tableheader_close(); 3165153720fSfkaag71 3175153720fSfkaag71 // render row content 3185153720fSfkaag71 $R->tablecell_open(); 3195153720fSfkaag71 $this->util->openField($mode, $R, $key); 3205153720fSfkaag71 for($i=0;$i<count($values);$i++) { 3215153720fSfkaag71 $triple =& $values[$i]; 3225153720fSfkaag71 if($i!=0) $R->cdata(', '); 3235153720fSfkaag71 $this->util->renderValue($mode, $R, $this->triples, $triple['value'], $triple['type'], $triple['hint']); 3245153720fSfkaag71 } 3255153720fSfkaag71 $this->util->closeField($mode, $R); 3265153720fSfkaag71 $R->tablecell_close(); 3275153720fSfkaag71 $R->tablerow_close(); 3285153720fSfkaag71 } 3295153720fSfkaag71 3305153720fSfkaag71 if($previousPosition || $nextPosition) { 3315153720fSfkaag71 $R->tablerow_open(); 3325153720fSfkaag71 $R->tableheader_open(2); 3335153720fSfkaag71 if($previousPosition) { 3345153720fSfkaag71 if($mode == 'xhtml') { $R->doc .= '<span class="strata-data-fragment-link-previous">'; } 3355153720fSfkaag71 $R->locallink($previousPosition, $this->util->getLang('data_entry_previous')); 3365153720fSfkaag71 if($mode == 'xhtml') { $R->doc .= '</span>'; } 3375153720fSfkaag71 } 3385153720fSfkaag71 $R->cdata(' '); 3395153720fSfkaag71 if($nextPosition) { 3405153720fSfkaag71 if($mode == 'xhtml') { $R->doc .= '<span class="strata-data-fragment-link-next">'; } 3415153720fSfkaag71 $R->locallink($nextPosition, $this->util->getLang('data_entry_next')); 3425153720fSfkaag71 if($mode == 'xhtml') { $R->doc .= '</span>'; } 3435153720fSfkaag71 } 3445153720fSfkaag71 $R->tableheader_close(); 3455153720fSfkaag71 $R->tablerow_close(); 3465153720fSfkaag71 } 3475153720fSfkaag71 3485153720fSfkaag71 $R->table_close(); 3495153720fSfkaag71 if($mode == 'xhtml') { $R->doc .= '</div>'; } 3505153720fSfkaag71 3515153720fSfkaag71 return true; 3525153720fSfkaag71 3535153720fSfkaag71 } elseif($mode == 'metadata' || $mode == 'preview_metadata') { 3545153720fSfkaag71 $triples = array(); 3555153720fSfkaag71 $subject = $ID.'#'.$data['entry']; 3565153720fSfkaag71 3575153720fSfkaag71 // resolve the subject to normalize everything 3585153720fSfkaag71 resolve_pageid(getNS($ID),$subject,$exists); 3595153720fSfkaag71 3605153720fSfkaag71 $titleKey = $this->util->getTitleKey(); 3615153720fSfkaag71 3625153720fSfkaag71 $fixTitle = false; 3635153720fSfkaag71 3645153720fSfkaag71 // we only use the title determination if no explicit title was given 3655153720fSfkaag71 if(empty($data['data'][$titleKey])) { 3665153720fSfkaag71 if(!empty($data['title candidate'])) { 3675153720fSfkaag71 // we have a candidate from somewhere 3685153720fSfkaag71 $data['data'][$titleKey][] = $data['title candidate']; 3695153720fSfkaag71 } else { 3705153720fSfkaag71 if(!empty($R->meta['title'])) { 3715153720fSfkaag71 // we do not have a candidate, so we use the page title 3725153720fSfkaag71 // (this is possible because fragments set the candidate) 3735153720fSfkaag71 $data['data'][$titleKey][] = array( 3745153720fSfkaag71 'value'=>$R->meta['title'], 3755153720fSfkaag71 'type'=>'text', 3765153720fSfkaag71 'hint'=>null 3775153720fSfkaag71 ); 3785153720fSfkaag71 } else { 3795153720fSfkaag71 // we were added before the page title is known 3805153720fSfkaag71 // however, we do require a page title (iff we actually store data) 3815153720fSfkaag71 $fixTitle = true; 3825153720fSfkaag71 } 3835153720fSfkaag71 } 3845153720fSfkaag71 } 3855153720fSfkaag71 3865153720fSfkaag71 // store positions information 3875153720fSfkaag71 if($mode == 'preview_metadata') { 3885153720fSfkaag71 self::$previewMetadata[$ID]['strata']['positions'][$data['entry']][] = $data['position']; 3895153720fSfkaag71 } else { 3905153720fSfkaag71 $R->meta['strata']['positions'][$data['entry']][] = $data['position']; 3915153720fSfkaag71 } 3925153720fSfkaag71 3935153720fSfkaag71 // process triples 3945153720fSfkaag71 foreach($data['data'] as $property=>$bucket) { 3955153720fSfkaag71 $this->util->renderPredicate($mode, $R, $this->triples, $property); 3965153720fSfkaag71 3975153720fSfkaag71 foreach($bucket as $triple) { 3985153720fSfkaag71 // render values for things like backlinks 3995153720fSfkaag71 $type = $this->util->loadType($triple['type']); 4005153720fSfkaag71 $type->render($mode, $R, $this->triples, $triple['value'], $triple['hint']); 4015153720fSfkaag71 4025153720fSfkaag71 // prepare triples for storage 4035153720fSfkaag71 $triples[] = array('subject'=>$subject, 'predicate'=>$property, 'object'=>$triple['value']); 4045153720fSfkaag71 } 4055153720fSfkaag71 } 4065153720fSfkaag71 4075153720fSfkaag71 // we're done if nodata is flagged. 4085153720fSfkaag71 if(!isset($R->info['data']) || $R->info['data']==true) { 4095153720fSfkaag71 // batch-store triples if we're allowed to store 4105153720fSfkaag71 $this->triples->addTriples($triples, $ID); 4115153720fSfkaag71 4125153720fSfkaag71 // set flag for title addendum 4135153720fSfkaag71 if($fixTitle) { 4145153720fSfkaag71 $R->meta['strata']['fixTitle'] = true; 4155153720fSfkaag71 } 4165153720fSfkaag71 } 4175153720fSfkaag71 4185153720fSfkaag71 return true; 4195153720fSfkaag71 } 4205153720fSfkaag71 4215153720fSfkaag71 return false; 4225153720fSfkaag71 } 4235153720fSfkaag71} 424