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']); } } } }