*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
// Check for presence of data plugin
$dataPluginFile = DOKU_PLUGIN.'data/syntax/table.php';
if(file_exists($dataPluginFile)){
require_once $dataPluginFile;
} else {
msg('datatemplate: Cannot find Data plugin.', -1);
return;
}
require_once(DOKU_PLUGIN.'datatemplate/syntax/inc/cache.php');
/**
* This inherits from the table syntax of the data plugin, because it's basically the
* same, just different output
*/
class syntax_plugin_datatemplate_list extends syntax_plugin_data_table {
var $dtc = null; // A cache instance
/**
* Constructor.
*/
function __construct(){
parent::__construct();
$this->dtc = new datatemplate_cache($this->dthlp);
}
/**
* Connect pattern to lexer
*/
function connectTo($mode) {
$this->Lexer->addSpecialPattern('----+ *datatemplatelist(?: [ a-zA-Z0-9_]*)?-+\n.*?\n----+',
$mode, 'plugin_datatemplate_list');
}
function handle($match, $state, $pos, Doku_Handler $handler){
// We want the parent to handle the parsing, but still accept
// the "template" paramter. So we need to remove the corresponding
// line from $match.
$template = '';
$lines = explode("\n", $match);
foreach ($lines as $num => $line) {
// ignore comments
$line = preg_replace('/(? '', 'key' => '%pageid%',
'title' => 'Title', 'type' => 'page');
if(array_key_exists('headers', $data))
array_push($data['headers'], '%pageid%');
}
return $data;
}
/**
* The _buildSQL routine of the data table class considers also filtering and
* limits passed via $_REQUEST. For efficient caching, we need to bypass these once in while.
* For this purpose, this function strips $_REQUEST of the unwanted fields before calling
* _buildSQL.
*
* @param array $data from the handle function
* @return string SQL
*/
function _buildSQL(&$data) {
// First remove unwanted fields.
$limit = $data['limit'];
$dataofs = $_REQUEST['dataofs'];
$dataflt = $_REQUEST['dataflt'];
unset($data['limit']);
unset($_REQUEST['dataofs']);
unset($_REQUEST['dataflt']);
$sql = parent::_buildSQL($data);
// Restore removed fields
$data['limit'] = $limit;
$_REQUEST['dataofs'] = $dataofs;
$_REQUEST['dataflt'] = $dataflt;
return $sql;
}
/**
* Create output
*/
function render($format, Doku_Renderer $R, $data) {
if(is_null($data)) return false;
$sql = $this->_buildSQL($data);
if($format == 'metadata') {
// Remove metadata from previous plugin versions
$this->dtc->removeMeta($R);
}
if($format == 'xhtml') {
$R->info['cache'] = false;
$this->dtc->checkAndBuildCache($data, $sql, $this);
if(!array_key_exists('template', $data)) {
// If keyword "template" not present, we will leave
// the rendering to the parent class.
msg("datatemplatelist: no template specified, using standard table output.");
return parent::render($format, $R, $data);
}
$datarows = $this->dtc->getData($sql);
$datarows = $this->_match_filters($data, $datarows);
if(count($datarows) < $_REQUEST['dataofs']) $_REQUEST['dataofs'] = 0;
$rows = array();
$i = 0;
$cnt = 0;
foreach($datarows as $row) {
$i++;
if($i - 1 < $_REQUEST['dataofs']) continue;
$rows[] = $row;
$cnt++;
if($data['limit'] && ($cnt == $data['limit'])) break; // keep an eye on the limit
}
if ($cnt === 0) {
$this->nullList($data, $clist = array(), $R);
return true;
}
$wikipage = preg_split('/\#/u', $data['template'], 2);
$R->doc .= $this->_renderPagination($data, count($datarows));
$this->_renderTemplate($wikipage[0], $data, $rows, $R);
$R->doc .= $this->_renderPagination($data, count($datarows));
return true;
}
return false;
}
/**
* Rendering of the template. The code is heavily inspired by the templater plugin by
* Jonathan Arkell. Not taken into consideration are correction of relative links in the
* template, and circular dependencies.
*
* @param string $wikipage the id of the wikipage containing the template
* @param array $data output of the handle function
* @param array $rows the result of the sql query
* @param Doku_Renderer_xhtml $R the dokuwiki renderer
* @return boolean Whether the page has been correctly (not: succesfully) processed.
*/
function _renderTemplate($wikipage, $data, $rows, &$R) {
global $ID;
resolve_pageid(getNS($ID), $wikipage, $exists); // resolve shortcuts
// check for permission
if (auth_quickaclcheck($wikipage) < 1) {
$R->doc .= '
No permissions to view the template
';
return true;
}
// Now open the template, parse it and do the substitutions.
// FIXME: This does not take circular dependencies into account!
$file = wikiFN($wikipage);
if (!@file_exists($file)) {
$R->doc .= '';
$R->doc .= "Template {$wikipage} not found. ";
$R->internalLink($wikipage, '[Click here to create it]');
$R->doc .= '
';
return true;
}
//collect column key names
$clist = array_keys($data['cols']);
// Construct replacement keys
foreach ($clist as $num => $head) {
$replacers['keys'][] = "@@" . $head . "@@";
$replacers['raw_keys'][] = "@@!" . $head . "@@";
}
// Get the raw file, and parse it into its instructions. This could be cached... maybe.
$rawFile = io_readfile($file);
// embed the included page
$R->doc .= "";
// We only want to call the parser once, so first do all the raw replacements and concatenate
// the strings.
$raw = "";
$i = 0;
$replacers['vals_id'] = array();
$replacers['keys_id'] = array();
foreach ($rows as $row) {
$replacers['keys_id'][$i] = array();
foreach($replacers['keys'] as $key) {
$replacers['keys_id'][$i][] = "@@[" . $i . "]" . substr($key,2);
}
$replacers['vals_id'][$i] = array();
$replacers['raw_vals'] = array();
foreach($row as $num => $cval) {
$replacers['raw_vals'][] = trim($cval);
$replacers['vals_id'][$i][] = $this->dthlp->_formatData($data['cols'][$clist[$num]], $cval, $R);
}
// First do raw replacements
$rawPart = str_replace($replacers['raw_keys'], $replacers['raw_vals'], $rawFile);
// Now mark all remaining keys with an index
$rawPart = str_replace($replacers['keys'], $replacers['keys_id'][$i], $rawPart);
$raw .= $rawPart;
$i++;
}
$instr = p_get_instructions($raw);
// render the instructructions on the fly
$text = p_render('xhtml', $instr, $info);
// remove toc, section edit buttons and category tags
$patterns = array('!
.*?(
\n
)!s',
'##e',
'!.*?
!s');
$replace = array('','','');
$text = preg_replace($patterns,$replace,$text);
// Do remaining replacements
foreach($replacers['vals_id'] as $num => $vals) {
$text = str_replace($replacers['keys_id'][$num], $vals, $text);
}
/** @deprecated 18 May 2013 column key names are used in stead of (localized) headers */
if(strpos($text, '@@Page@@') !== false) {
msg("datatemplate plugin: Use of @@Page@@ in '{$wikipage}' is deprecated. Replace it by @@%title%@@ please.", -1);
}
// Replace unused placeholders by empty string
$text = preg_replace('/@@.*?@@/', '', $text);
$R->doc .= $text;
$R->doc .= '';
return true;
}
/**
* Render page navigation area if applicable.
*
* @param array $data The output of the handle function.
* @param int $numrows the total number of rows in the sql result.
* @return string The html for the pagination.
*/
function _renderPagination($data, $numrows) {
global $ID;
$text = '';
// Add pagination controls
if($data['limit']){
$params = $this->dthlp->_a2ua('dataflt',$_REQUEST['dataflt']);
//$params['datasrt'] = $_REQUEST['datasrt'];
$offset = (int) $_REQUEST['dataofs'];
if($offset){
$prev = $offset - $data['limit'];
if($prev < 0) $prev = 0;
// keep url params
$params['dataofs'] = $prev;
$text .= ''.'← '.$this->getLang('prevpage').'';
} else {
$text .= '← '.$this->getLang('prevpage').'';
}
for($i=1; $i <= ceil($numrows / $data['limit']); $i++) {
$offs = ($i - 1) * $data['limit'];
$params['dataofs'] = $offs;
$selected = $offs == $_REQUEST['dataofs'] ? ' class="selected"': '';
$text .= '' . $i. '';
}
if($numrows - $offset > $data['limit']){
$next = $offset + $data['limit'];
// keep url params
$params['dataofs'] = $next;
$text .= ''.$this->getLang('nextpage').' →'.'';
} else {
$text .= ''.$this->getLang('nextpage').' →';
}
return '' . $text . '
';
}
return $text;
}
/**
* Apply filters to the (unfiltered) sql output.
*
* @param array $data The output of the handle function.
* @param array $datarows The output of the sql request
* @return array The filtered sql output.
*/
function _match_filters($data, $datarows) {
/* Get whole $data as input and
* - generate keys
* - treat multi-value columns specially, i.e. add 's' to key and look at individual values
*/
$out = array();
$keys = array();
foreach($data['headers'] as $k => $v) {
$keys[$v] = $k;
}
$filters = $this->dthlp->_get_filters();
if(!$datarows) return $out;
foreach($datarows as $dr) {
$matched = True;
$datarow = array_values($dr);
foreach($filters as $f) {
if (strcasecmp($f['key'], 'any') == 0) {
$cols = array_keys($keys);
} else {
$cols = array($f['key']);
}
$colmatch = False;
foreach($cols as $col) {
$multi = $data['cols'][$col]['multi'];
if($multi) $col .= 's';
$idx = $keys[$col];
switch($f['compare']) {
case 'LIKE':
$comp = $this->_match_wildcard($f['value'], $datarow[$idx]);
break;
case 'NOT LIKE':
$comp = !$this->_match_wildcard($f['value'], $datarow[$idx]);
break;
case '=':
$f['compare'] = '==';
default:
$evalstr = $datarow[$idx] . $f['compare'] . $f['value'];
$comp = eval('return ' . $evalstr . ';');
}
$colmatch = $colmatch || $comp;
}
if($f['logic'] == 'AND') {
$matched = $matched && $colmatch;
} else {
$matched = $matched || $colmatch;
}
}
if($matched) $out[] = $dr;
}
return $out;
}
/**
* Match string against SQL wildcards.
* @param $wildcard_pattern
* @param $haystack
* @return boolean Whether the pattern matches.
*/
function _match_wildcard( $wildcard_pattern, $haystack ) {
$regex = str_replace(array("%", "\?"), // wildcard chars
array('.*','.'), // regexp chars
preg_quote($wildcard_pattern)
);
return preg_match('/^\s*'.$regex.'$/im', $haystack);
}
function nullList($data, $clist, &$R) {
$R->doc .= 'Nothing.
';
}
}
/* Local Variables: */
/* c-basic-offset: 4 */
/* End: */