mirror of
https://github.com/cosmocode/dokuwiki-plugin-struct.git
synced 2025-07-21 23:42:40 +00:00
Value aggregation type (#386)
* Allow a single value to be used inline in the page * Removed debug line * Added short form of value syntax - {{$s.f}} * Refactor inline parsing into a new class and add filtering for page ID * Allow recovery if the user has used single quotes * Fixed metadata rendering when using $INFO['id'] * Handle no result better * Add configuration for whether to show a no records text * Linter fixes * Match array style with other entries * Simplify tokenising with -> separator * Replace use of GLOBALS array * Revert to dots, remove block syntax * Suggestion of future syntax * Use space-separated parameters * Explanatory comments * Increase sort order * Don't lint for errors required by DokuWiki * Move syntax description to class documentation * Draft InlineConfigParser test * Docstring alignment
This commit is contained in:
59
_test/InlineConfigParser.test.php
Normal file
59
_test/InlineConfigParser.test.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\struct\test;
|
||||
|
||||
use dokuwiki\plugin\struct\meta;
|
||||
|
||||
/**
|
||||
* Tests for parsing the inline aggregation config for the struct plugin
|
||||
*
|
||||
* @group plugin_struct
|
||||
* @group plugins
|
||||
*
|
||||
*/
|
||||
// phpcs:ignore Squiz.Classes.ValidClassName
|
||||
class InlineConfigParser_struct_test extends StructTest
|
||||
{
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function test_simple()
|
||||
{
|
||||
// Same initial setup as ConfigParser.test
|
||||
$inline = '"testtable, another, foo bar"."%pageid%, count" ';
|
||||
$inline .= '?sort: ^count sort: "%pageid%, ^bam" align: "r,l,center,foo"';
|
||||
// Add InlineConfigParser-specific tests:
|
||||
$inline .= '& "%pageid% != start" | "count = 1"';
|
||||
|
||||
$configParser = new meta\InlineConfigParser($inline);
|
||||
$actual_config = $configParser->getConfig();
|
||||
|
||||
$expected_config = [
|
||||
'align' => ['right', 'left', 'center', null],
|
||||
'cols' => ['%pageid%', 'count'],
|
||||
'csv' => true,
|
||||
'dynfilters' => false,
|
||||
'filter' => [
|
||||
['%pageid%', '!=', 'start', 'AND'],
|
||||
['count', '=', '1', 'OR' ],
|
||||
],
|
||||
'headers' => [null, null],
|
||||
'limit' => 0,
|
||||
'rownumbers' => false,
|
||||
'schemas' => [
|
||||
['testtable', '' ],
|
||||
['another', '' ],
|
||||
['foo', 'bar'],
|
||||
],
|
||||
'sepbyheaders' => false,
|
||||
'sort' => [
|
||||
['count', false],
|
||||
['%pageid%', true ],
|
||||
['bam', false],
|
||||
],
|
||||
'summarize' => false,
|
||||
'target' => '',
|
||||
'widths' => [],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected_config, $actual_config);
|
||||
}
|
||||
}
|
@ -3,3 +3,4 @@
|
||||
$conf['bottomoutput'] = 0;
|
||||
$conf['topoutput'] = 0;
|
||||
$conf['disableDeleteSerial'] = 0;
|
||||
$conf['show_not_found'] = 1;
|
||||
|
@ -3,3 +3,4 @@
|
||||
$meta['bottomoutput'] = ['onoff'];
|
||||
$meta['topoutput'] = ['onoff'];
|
||||
$meta['disableDeleteSerial'] = ['onoff'];
|
||||
$meta['show_not_found'] = ['onoff'];
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
|
||||
$lang['bottomoutput'] = 'Display data at the bottom of the page';
|
||||
$lang['topoutput'] = 'Display data at the top of the page';
|
||||
$lang['disableDeleteSerial'] = 'Disable delete button for serial data';
|
||||
$lang['show_not_found'] = 'Show the default text when no results are returned for struct value syntax';
|
||||
|
162
meta/AggregationValue.php
Normal file
162
meta/AggregationValue.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\struct\meta;
|
||||
|
||||
/**
|
||||
* Class AggregationValue
|
||||
*
|
||||
* @package dokuwiki\plugin\struct\meta
|
||||
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
|
||||
* @author Iain Hallam <iain@nineworlds.net>
|
||||
*/
|
||||
class AggregationValue
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string the page id of the page this is rendered to
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @var string the Type of renderer used
|
||||
*/
|
||||
protected $mode;
|
||||
/**
|
||||
* @var Doku_Renderer the DokuWiki renderer used to create the output
|
||||
*/
|
||||
protected $renderer;
|
||||
/**
|
||||
* @var SearchConfig the configured search - gives access to columns etc.
|
||||
*/
|
||||
protected $searchConfig;
|
||||
|
||||
/**
|
||||
* @var Column the column to be displayed
|
||||
*/
|
||||
protected $column;
|
||||
|
||||
/**
|
||||
* @var Value[][] the search result
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var int number of all results
|
||||
*/
|
||||
protected $resultCount;
|
||||
|
||||
/**
|
||||
* @todo we might be able to get rid of this helper and move this to SearchConfig
|
||||
* @var helper_plugin_struct_config
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
* Initialize the Aggregation renderer and executes the search
|
||||
*
|
||||
* You need to call @see render() on the resulting object.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $mode
|
||||
* @param Doku_Renderer $renderer
|
||||
* @param SearchConfig $searchConfig
|
||||
*/
|
||||
public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig)
|
||||
{
|
||||
// Parameters
|
||||
$this->id = $id;
|
||||
$this->mode = $mode;
|
||||
$this->renderer = $renderer;
|
||||
$this->searchConfig = $searchConfig;
|
||||
|
||||
// Search info
|
||||
$this->data = $this->searchConfig->getConf();
|
||||
$columns = $this->searchConfig->getColumns();
|
||||
$this->column = $columns[0];
|
||||
|
||||
// limit to first result
|
||||
$this->searchConfig->setLimit(1);
|
||||
$this->searchConfig->setOffset(0);
|
||||
|
||||
// Run the search
|
||||
$result = $this->searchConfig->execute();
|
||||
$this->resultCount = $this->searchConfig->getCount();
|
||||
|
||||
// Change from two-dimensional array with one entry to one-dimensional array
|
||||
$this->result = $result[0];
|
||||
|
||||
// Load helper
|
||||
$this->helper = plugin_load('helper', 'struct_config');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the output on the renderer
|
||||
*
|
||||
* @param int $show_not_found Whether to display the default text for no records
|
||||
*/
|
||||
public function render($show_not_found = 0)
|
||||
{
|
||||
$this->startScope();
|
||||
|
||||
// Check that we actually got a result
|
||||
if ($this->resultCount) {
|
||||
$this->renderValue($this->result);
|
||||
} else {
|
||||
if ($show_not_found) {
|
||||
$this->renderer->cdata($this->helper->getLang('none'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->finishScope();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional info to document and renderer in XHTML mode
|
||||
*
|
||||
* @see finishScope()
|
||||
*/
|
||||
protected function startScope()
|
||||
{
|
||||
// wrapping span
|
||||
if ($this->mode != 'xhtml') {
|
||||
return;
|
||||
}
|
||||
$this->renderer->doc .= "<span class=\"structaggregation valueaggregation\">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes anything opened in startScope()
|
||||
*
|
||||
* @see startScope()
|
||||
*/
|
||||
protected function finishScope()
|
||||
{
|
||||
// wrapping span
|
||||
if ($this->mode != 'xhtml') {
|
||||
return;
|
||||
}
|
||||
$this->renderer->doc .= '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $resultrow
|
||||
*/
|
||||
protected function renderValue($resultrow)
|
||||
{
|
||||
// @var Value $value
|
||||
foreach ($resultrow as $column => $value) {
|
||||
if ($value->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if ($this->mode == 'xhtml') {
|
||||
$type = 'struct_' . strtolower($value->getColumn()->getType()->getClass());
|
||||
$this->renderer->doc .= '<span class="' . $type . '">';
|
||||
}
|
||||
$value->render($this->renderer, $this->mode);
|
||||
if ($this->mode == 'xhtml') {
|
||||
$this->renderer->doc .= '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
meta/InlineConfigParser.php
Normal file
103
meta/InlineConfigParser.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\plugin\struct\meta;
|
||||
|
||||
/**
|
||||
* Class InlineConfigParser
|
||||
*
|
||||
* Wrapper to convert inline syntax to full before instantiating ConfigParser
|
||||
*
|
||||
* {{$schema.field}}
|
||||
* {{$pageid.schema.field}}
|
||||
* {{$... ? filter: ... and: ... or: ...}} or {{$... ? & ... | ...}}
|
||||
* TODO: {{$... ? sum}} or {{$... ? +}}
|
||||
* TODO: {{$... ? default: ...}} or {{$... ? ! ...}}
|
||||
* Colons following key words must have no space preceding them.
|
||||
* If no page ID or filter is supplied, filter: "%pageid% = $ID$" is added.
|
||||
* Any component can be placed in double quotes (needed to allow space, dot or question mark in components).
|
||||
*
|
||||
* @package dokuwiki\plugin\struct\meta
|
||||
*/
|
||||
class InlineConfigParser extends ConfigParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Parser constructor.
|
||||
*
|
||||
* parses the given inline configuration
|
||||
*
|
||||
* @param string $inline
|
||||
*/
|
||||
public function __construct($inline)
|
||||
{
|
||||
// Start to build the main config array
|
||||
$lines = array(); // Config lines to pass to full parser
|
||||
|
||||
// Extract components
|
||||
$parts = explode('?', $inline, 2);
|
||||
$n_parts = count($parts);
|
||||
$components = str_getcsv(trim($parts[0]), '.');
|
||||
$n_components = count($components);
|
||||
|
||||
// Extract parameters if given
|
||||
if ($n_parts == 2) {
|
||||
$filtering = false; // Whether to filter result to current page
|
||||
$parameters = str_getcsv(trim($parts[1]), ' ');
|
||||
$n_parameters = count($parameters);
|
||||
|
||||
// Process parameters and add to config lines
|
||||
for ($i = 0; $i < $n_parameters; $i++) {
|
||||
$p = trim($parameters[$i]);
|
||||
switch ($p) {
|
||||
// Empty (due to extra spaces)
|
||||
case '':
|
||||
// Move straight to next parameter
|
||||
continue 2;
|
||||
break;
|
||||
// Pass full text ending in : straight to config
|
||||
case $p[-1] == ':' ? $p : '':
|
||||
if (in_array($p, ['filter', 'where', 'filterand', 'and', 'filteror','or'])) {
|
||||
$filtering = true;
|
||||
}
|
||||
$lines[] = $p . ' ' . trim($parameters[$i + 1]);
|
||||
$i++;
|
||||
break;
|
||||
// Short alias for filterand
|
||||
case '&':
|
||||
$filtering = true;
|
||||
$lines[] = 'filterand: ' . trim($parameters[$i + 1]);
|
||||
$i++;
|
||||
break;
|
||||
// Short alias for filteror
|
||||
case '|':
|
||||
$filtering = true;
|
||||
$lines[] = 'filteror: ' . trim($parameters[$i + 1]);
|
||||
$i++;
|
||||
break;
|
||||
default:
|
||||
// Move straight to next parameter
|
||||
continue 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a page was specified
|
||||
if (count($components) == 3) {
|
||||
// At least page, schema and field supplied
|
||||
$lines[] = 'schema: ' . trim($components[1]);
|
||||
$lines[] = 'field: ' . trim($components[2]);
|
||||
$lines[] = 'filter: %pageid% = ' . trim($components[0]);
|
||||
} elseif (count($components) == 2) {
|
||||
// At least schema and field supplied
|
||||
$lines[] = 'schema: ' . trim($components[0]);
|
||||
$lines[] = 'field: ' . trim($components[1]);
|
||||
if (! $filtering) {
|
||||
$lines[] = 'filter: %pageid% = $ID$';
|
||||
}
|
||||
}
|
||||
|
||||
// Call original ConfigParser's constructor
|
||||
parent::__construct($lines);
|
||||
}
|
||||
}
|
128
syntax/value.php
Normal file
128
syntax/value.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* DokuWiki Plugin struct (Syntax Component)
|
||||
*
|
||||
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
|
||||
* @author Iain Hallam <iain@nineworlds.net>
|
||||
*/
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
|
||||
use dokuwiki\plugin\struct\meta\AggregationValue;
|
||||
use dokuwiki\plugin\struct\meta\ConfigParser;
|
||||
use dokuwiki\plugin\struct\meta\InlineConfigParser;
|
||||
use dokuwiki\plugin\struct\meta\SearchConfig;
|
||||
use dokuwiki\plugin\struct\meta\StructException;
|
||||
|
||||
// must be run within Dokuwiki
|
||||
if (!defined('DOKU_INC')) {
|
||||
die();
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Classes.ClassDeclaration, Squiz.Classes.ValidClassName
|
||||
class syntax_plugin_struct_value extends DokuWiki_Syntax_Plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* @return string Syntax mode type
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return 'substition';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Sort order - Low numbers go before high numbers
|
||||
*/
|
||||
public function getSort()
|
||||
{
|
||||
/* 315 to place above Doku_Parser_Mode_media, which would
|
||||
* otherwise take precedence. See
|
||||
* https://www.dokuwiki.org/devel:parser:getsort_list
|
||||
*/
|
||||
return 315;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect lookup pattern to lexer.
|
||||
*
|
||||
* @param string $mode Parser mode
|
||||
*/
|
||||
public function connectTo($mode)
|
||||
{
|
||||
// {{$...}}
|
||||
$this->Lexer->addSpecialPattern('\{\{\$[^}]+\}\}', $mode, 'plugin_struct_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle matches of the struct syntax
|
||||
*
|
||||
* @param string $match The match of the syntax
|
||||
* @param int $state The state of the handler
|
||||
* @param int $pos The position in the document
|
||||
* @param Doku_Handler $handler The handler
|
||||
* @return array Data for the renderer
|
||||
*/
|
||||
public function handle($match, $state, $pos, Doku_Handler $handler)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
try {
|
||||
// strip {{$ and }} markers
|
||||
$inline = substr($match, 3, -2);
|
||||
|
||||
// Parse inline syntax
|
||||
$parser = new InlineConfigParser($inline);
|
||||
$config = $parser->getConfig();
|
||||
|
||||
return $config;
|
||||
} catch (StructException $e) {
|
||||
msg($e->getMessage(), -1, $e->getLine(), $e->getFile());
|
||||
if ($conf['allowdebug']) {
|
||||
msg('<pre>' . hsc($e->getTraceAsString()) . '</pre>', -1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render xhtml output or metadata
|
||||
*
|
||||
* @param string $mode Renderer mode (supported modes: xhtml)
|
||||
* @param Doku_Renderer $renderer The renderer
|
||||
* @param array $data The data from the handler() function
|
||||
* @return bool If rendering was successful.
|
||||
*/
|
||||
public function render($mode, Doku_Renderer $renderer, $data)
|
||||
{
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
global $INFO;
|
||||
global $conf;
|
||||
|
||||
// Get configuration
|
||||
$show_not_found = $this->getConf('show_not_found');
|
||||
|
||||
try {
|
||||
/** @var SearchConfig $search */
|
||||
$search = new SearchConfig($data);
|
||||
|
||||
/** @var AggregationValue $value */
|
||||
$value = new AggregationValue($INFO['id'], $mode, $renderer, $search);
|
||||
$value->render($show_not_found);
|
||||
|
||||
if ($mode == 'metadata') {
|
||||
/** @var Doku_Renderer_metadata $renderer */
|
||||
$renderer->meta['plugin']['struct']['hasaggregation'] = $search->getCacheFlag();
|
||||
}
|
||||
} catch (StructException $e) {
|
||||
msg($e->getMessage(), -1, $e->getLine(), $e->getFile());
|
||||
if ($conf['allowdebug']) {
|
||||
msg('<pre>' . hsc($e->getTraceAsString()) . '</pre>', -1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user