mirror of
https://github.com/cosmocode/dokuwiki-plugin-struct.git
synced 2025-07-21 23:42:40 +00:00
270 lines
7.9 KiB
PHP
270 lines
7.9 KiB
PHP
<?php
|
|
|
|
namespace dokuwiki\plugin\struct\types;
|
|
|
|
use dokuwiki\File\PageResolver;
|
|
use dokuwiki\plugin\struct\meta\QueryBuilder;
|
|
use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
|
|
use dokuwiki\plugin\struct\meta\StructException;
|
|
use dokuwiki\Utf8\PhpString;
|
|
|
|
/**
|
|
* Class Page
|
|
*
|
|
* Represents a single page in the wiki. Will be linked in output.
|
|
*
|
|
* @package dokuwiki\plugin\struct\types
|
|
*/
|
|
class Page extends AbstractMultiBaseType
|
|
{
|
|
protected $config = [
|
|
'usetitles' => false,
|
|
'autocomplete' => [
|
|
'mininput' => 2,
|
|
'maxresult' => 5,
|
|
'filter' => '',
|
|
]
|
|
];
|
|
|
|
/**
|
|
* Output the stored data
|
|
*
|
|
* @param string $value the value stored in the database - JSON when titles are used
|
|
* @param \Doku_Renderer $R the renderer currently used to render the data
|
|
* @param string $mode The mode the output is rendered in (eg. XHTML)
|
|
* @return bool true if $mode could be satisfied
|
|
*/
|
|
public function renderValue($value, \Doku_Renderer $R, $mode)
|
|
{
|
|
if ($this->config['usetitles']) {
|
|
[$id, $title] = \helper_plugin_struct::decodeJson($value);
|
|
} else {
|
|
$id = $value;
|
|
$title = $id; // cannot be empty or internallink() might hijack %pageid% and use headings
|
|
}
|
|
|
|
if (!$id) return true;
|
|
|
|
$R->internallink(":$id", $title);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Cleans the link
|
|
*
|
|
* @param string $rawvalue
|
|
* @return string
|
|
*/
|
|
public function validate($rawvalue)
|
|
{
|
|
[$page, $fragment] = array_pad(explode('#', $rawvalue, 2), 2, '');
|
|
return cleanID($page) . (strlen(cleanID($fragment)) > 0 ? '#' . cleanID($fragment) : '');
|
|
}
|
|
|
|
/**
|
|
* Autocompletion support for pages
|
|
*
|
|
* @return array
|
|
*/
|
|
public function handleAjax()
|
|
{
|
|
global $INPUT;
|
|
|
|
// check minimum length
|
|
$lookup = trim($INPUT->str('search'));
|
|
if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return [];
|
|
|
|
// results wanted?
|
|
$max = $this->config['autocomplete']['maxresult'];
|
|
if ($max <= 0) return [];
|
|
|
|
$data = ft_pageLookup($lookup, true, $this->config['usetitles']);
|
|
if ($data === []) return [];
|
|
|
|
$filter = $this->config['autocomplete']['filter'];
|
|
|
|
// this basically duplicates what we do in ajax_qsearch() but with a filter
|
|
$result = [];
|
|
$counter = 0;
|
|
foreach ($data as $id => $title) {
|
|
if (!empty($filter) && !$this->filterMatch($id, $filter)) {
|
|
continue;
|
|
}
|
|
if ($this->config['usetitles']) {
|
|
$name = $title . ' (' . $id . ')';
|
|
} else {
|
|
$ns = getNS($id);
|
|
if ($ns) {
|
|
$name = noNS($id) . ' (' . $ns . ')';
|
|
} else {
|
|
$name = $id;
|
|
}
|
|
}
|
|
|
|
$result[] = [
|
|
'label' => $name,
|
|
'value' => $id
|
|
];
|
|
|
|
$counter++;
|
|
if ($counter > $max) break;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* When using titles, we need ot join the titles table
|
|
*
|
|
* @param QueryBuilder $QB
|
|
* @param string $tablealias
|
|
* @param string $colname
|
|
* @param string $alias
|
|
*/
|
|
public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
|
|
{
|
|
if (!$this->config['usetitles']) {
|
|
parent::select($QB, $tablealias, $colname, $alias);
|
|
return;
|
|
}
|
|
$rightalias = $QB->generateTableAlias();
|
|
$QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
|
|
$QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $rightalias.title)", $alias);
|
|
}
|
|
|
|
/**
|
|
* When using titles, sort by them first
|
|
*
|
|
* @param QueryBuilder $QB
|
|
* @param string $tablealias
|
|
* @param string $colname
|
|
* @param string $order
|
|
*/
|
|
public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
|
|
{
|
|
if (!$this->config['usetitles']) {
|
|
parent::sort($QB, $tablealias, $colname, $order);
|
|
return;
|
|
}
|
|
|
|
$rightalias = $QB->generateTableAlias();
|
|
$QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
|
|
$QB->addOrderBy("$rightalias.title COLLATE NOCASE $order");
|
|
$QB->addOrderBy("$tablealias.$colname $order");
|
|
}
|
|
|
|
/**
|
|
* Return the pageid only
|
|
*
|
|
* @param string $value
|
|
* @return string
|
|
*/
|
|
public function rawValue($value)
|
|
{
|
|
if ($this->config['usetitles']) {
|
|
[$value] = \helper_plugin_struct::decodeJson($value);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Return the title only
|
|
*
|
|
* @param string $value
|
|
* @return string
|
|
*/
|
|
public function displayValue($value)
|
|
{
|
|
if ($this->config['usetitles']) {
|
|
[$pageid, $value] = \helper_plugin_struct::decodeJson($value);
|
|
if (blank($value)) {
|
|
$value = $pageid;
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* When using titles, we need to compare against the title table, too
|
|
*
|
|
* @param QueryBuilderWhere $add
|
|
* @param string $tablealias
|
|
* @param string $colname
|
|
* @param string $comp
|
|
* @param string $value
|
|
* @param string $op
|
|
*/
|
|
public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
|
|
{
|
|
if (!$this->config['usetitles']) {
|
|
parent::filter($add, $tablealias, $colname, $comp, $value, $op);
|
|
return;
|
|
}
|
|
|
|
$QB = $add->getQB();
|
|
$rightalias = $QB->generateTableAlias();
|
|
$QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
|
|
|
|
// compare against page and title
|
|
$sub = $add->where($op);
|
|
$pl = $QB->addValue($value);
|
|
$sub->whereOr("$tablealias.$colname $comp $pl");
|
|
$pl = $QB->addValue($value);
|
|
$sub->whereOr("$rightalias.title $comp $pl");
|
|
}
|
|
|
|
/**
|
|
* Check if the given id matches a configured filter pattern
|
|
*
|
|
* @param string $id
|
|
* @param string $filter
|
|
* @return bool
|
|
*/
|
|
public function filterMatch($id, $filter)
|
|
{
|
|
// absolute namespace?
|
|
if (PhpString::substr($filter, 0, 1) === ':') {
|
|
$filter = '^' . $filter;
|
|
}
|
|
|
|
try {
|
|
$check = preg_match('/' . $filter . '/', ':' . $id, $matches);
|
|
} catch (\Exception $e) {
|
|
throw new StructException("Error processing regular expression '$filter'");
|
|
}
|
|
return (bool)$check;
|
|
}
|
|
|
|
/**
|
|
* Merge the current config with the base config of the type.
|
|
*
|
|
* In contrast to parent, this method does not throw away unknown keys.
|
|
* Required to migrate deprecated / obsolete options, no longer part of type config.
|
|
*
|
|
* @param array $current Current configuration
|
|
* @param array $config Base Type configuration
|
|
*/
|
|
protected function mergeConfig($current, &$config)
|
|
{
|
|
foreach ($current as $key => $value) {
|
|
if (isset($config[$key]) && is_array($config[$key])) {
|
|
$this->mergeConfig($value, $config[$key]);
|
|
} else {
|
|
$config[$key] = $value;
|
|
}
|
|
}
|
|
|
|
// migrate autocomplete options 'namespace' and 'postfix' to 'filter'
|
|
if (empty($config['autocomplete']['filter'])) {
|
|
if (!empty($config['autocomplete']['namespace'])) {
|
|
$config['autocomplete']['filter'] = $config['autocomplete']['namespace'];
|
|
unset($config['autocomplete']['namespace']);
|
|
}
|
|
if (!empty($config['autocomplete']['postfix'])) {
|
|
$config['autocomplete']['filter'] .= '.+?' . $config['autocomplete']['postfix'] . '$';
|
|
unset($config['autocomplete']['postfix']);
|
|
}
|
|
}
|
|
}
|
|
}
|