Initial commit
This commit is contained in:
167
content/inc/Ui/Admin.php
Normal file
167
content/inc/Ui/Admin.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Admin
|
||||
*
|
||||
* Displays the Admin screen
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Håkan Sandell <hakan.sandell@home.se>
|
||||
*/
|
||||
class Admin extends Ui {
|
||||
|
||||
protected $forAdmins = array('usermanager', 'acl', 'extension', 'config', 'styling');
|
||||
protected $forManagers = array('revert', 'popularity');
|
||||
/** @var array[] */
|
||||
protected $menu;
|
||||
|
||||
/**
|
||||
* Display the UI element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show() {
|
||||
$this->menu = $this->getPluginList();
|
||||
echo '<div class="ui-admin">';
|
||||
echo p_locale_xhtml('admin');
|
||||
$this->showSecurityCheck();
|
||||
$this->showMenu('admin');
|
||||
$this->showMenu('manager');
|
||||
$this->showVersion();
|
||||
$this->showMenu('other');
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the given menu of available plugins
|
||||
*
|
||||
* @param string $type admin|manager|other
|
||||
*/
|
||||
protected function showMenu($type) {
|
||||
if (!$this->menu[$type]) return;
|
||||
|
||||
if ($type === 'other') {
|
||||
echo p_locale_xhtml('adminplugins');
|
||||
$class = 'admin_plugins';
|
||||
} else {
|
||||
$class = 'admin_tasks';
|
||||
}
|
||||
|
||||
echo "<ul class=\"$class\">";
|
||||
foreach ($this->menu[$type] as $item) {
|
||||
$this->showMenuItem($item);
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the DokuWiki version
|
||||
*/
|
||||
protected function showVersion() {
|
||||
echo '<div id="admin__version">';
|
||||
echo getVersion();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* data security check
|
||||
*
|
||||
* simple check if the 'savedir' is relative and accessible when appended to DOKU_URL
|
||||
*
|
||||
* it verifies either:
|
||||
* 'savedir' has been moved elsewhere, or
|
||||
* has protection to prevent the webserver serving files from it
|
||||
*/
|
||||
protected function showSecurityCheck() {
|
||||
global $conf;
|
||||
if(substr($conf['savedir'], 0, 2) !== './') return;
|
||||
$img = DOKU_URL . $conf['savedir'] .
|
||||
'/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png';
|
||||
echo '<a style="border:none; float:right;"
|
||||
href="http://www.dokuwiki.org/security#web_access_security">
|
||||
<img src="' . $img . '" alt="Your data directory seems to be protected properly."
|
||||
onerror="this.parentNode.style.display=\'none\'" /></a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a single Admin menu item
|
||||
*
|
||||
* @param array $item
|
||||
*/
|
||||
protected function showMenuItem($item) {
|
||||
global $ID;
|
||||
if(blank($item['prompt'])) return;
|
||||
echo '<li><div class="li">';
|
||||
echo '<a href="' . wl($ID, 'do=admin&page=' . $item['plugin']) . '">';
|
||||
echo '<span class="icon">';
|
||||
echo inlineSVG($item['icon']);
|
||||
echo '</span>';
|
||||
echo '<span class="prompt">';
|
||||
echo $item['prompt'];
|
||||
echo '</span>';
|
||||
echo '</a>';
|
||||
echo '</div></li>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build list of admin functions from the plugins that handle them
|
||||
*
|
||||
* Checks the current permissions to decide on manager or admin plugins
|
||||
*
|
||||
* @return array list of plugins with their properties
|
||||
*/
|
||||
protected function getPluginList() {
|
||||
global $conf;
|
||||
|
||||
$pluginlist = plugin_list('admin');
|
||||
$menu = ['admin' => [], 'manager' => [], 'other' => []];
|
||||
|
||||
foreach($pluginlist as $p) {
|
||||
/** @var \dokuwiki\Extension\AdminPlugin $obj */
|
||||
if(($obj = plugin_load('admin', $p)) === null) continue;
|
||||
|
||||
// check permissions
|
||||
if (!$obj->isAccessibleByCurrentUser()) continue;
|
||||
|
||||
if (in_array($p, $this->forAdmins, true)) {
|
||||
$type = 'admin';
|
||||
} elseif (in_array($p, $this->forManagers, true)){
|
||||
$type = 'manager';
|
||||
} else {
|
||||
$type = 'other';
|
||||
}
|
||||
|
||||
$menu[$type][$p] = array(
|
||||
'plugin' => $p,
|
||||
'prompt' => $obj->getMenuText($conf['lang']),
|
||||
'icon' => $obj->getMenuIcon(),
|
||||
'sort' => $obj->getMenuSort(),
|
||||
);
|
||||
}
|
||||
|
||||
// sort by name, then sort
|
||||
uasort($menu['admin'], [$this, 'menuSort']);
|
||||
uasort($menu['manager'], [$this, 'menuSort']);
|
||||
uasort($menu['other'], [$this, 'menuSort']);
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sorting for admin menu
|
||||
*
|
||||
* We sort alphabetically first, then by sort value
|
||||
*
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
* @return int
|
||||
*/
|
||||
protected function menuSort($a, $b) {
|
||||
$strcmp = strcasecmp($a['prompt'], $b['prompt']);
|
||||
if($strcmp != 0) return $strcmp;
|
||||
if($a['sort'] === $b['sort']) return 0;
|
||||
return ($a['sort'] < $b['sort']) ? -1 : 1;
|
||||
}
|
||||
}
|
647
content/inc/Ui/Search.php
Normal file
647
content/inc/Ui/Search.php
Normal file
@@ -0,0 +1,647 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Form\Form;
|
||||
|
||||
class Search extends Ui
|
||||
{
|
||||
protected $query;
|
||||
protected $parsedQuery;
|
||||
protected $searchState;
|
||||
protected $pageLookupResults = array();
|
||||
protected $fullTextResults = array();
|
||||
protected $highlight = array();
|
||||
|
||||
/**
|
||||
* Search constructor.
|
||||
*
|
||||
* @param array $pageLookupResults pagename lookup results in the form [pagename => pagetitle]
|
||||
* @param array $fullTextResults fulltext search results in the form [pagename => #hits]
|
||||
* @param array $highlight array of strings to be highlighted
|
||||
*/
|
||||
public function __construct(array $pageLookupResults, array $fullTextResults, $highlight)
|
||||
{
|
||||
global $QUERY;
|
||||
$Indexer = idx_get_indexer();
|
||||
|
||||
$this->query = $QUERY;
|
||||
$this->parsedQuery = ft_queryParser($Indexer, $QUERY);
|
||||
$this->searchState = new SearchState($this->parsedQuery);
|
||||
|
||||
$this->pageLookupResults = $pageLookupResults;
|
||||
$this->fullTextResults = $fullTextResults;
|
||||
$this->highlight = $highlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* display the search result
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$searchHTML = '';
|
||||
|
||||
$searchHTML .= $this->getSearchIntroHTML($this->query);
|
||||
|
||||
$searchHTML .= $this->getSearchFormHTML($this->query);
|
||||
|
||||
$searchHTML .= $this->getPageLookupHTML($this->pageLookupResults);
|
||||
|
||||
$searchHTML .= $this->getFulltextResultsHTML($this->fullTextResults, $this->highlight);
|
||||
|
||||
echo $searchHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a form which can be used to adjust/refine the search
|
||||
*
|
||||
* @param string $query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getSearchFormHTML($query)
|
||||
{
|
||||
global $lang, $ID, $INPUT;
|
||||
|
||||
$searchForm = (new Form(['method' => 'get'], true))->addClass('search-results-form');
|
||||
$searchForm->setHiddenField('do', 'search');
|
||||
$searchForm->setHiddenField('id', $ID);
|
||||
$searchForm->setHiddenField('sf', '1');
|
||||
if ($INPUT->has('min')) {
|
||||
$searchForm->setHiddenField('min', $INPUT->str('min'));
|
||||
}
|
||||
if ($INPUT->has('max')) {
|
||||
$searchForm->setHiddenField('max', $INPUT->str('max'));
|
||||
}
|
||||
if ($INPUT->has('srt')) {
|
||||
$searchForm->setHiddenField('srt', $INPUT->str('srt'));
|
||||
}
|
||||
$searchForm->addFieldsetOpen()->addClass('search-form');
|
||||
$searchForm->addTextInput('q')->val($query)->useInput(false);
|
||||
$searchForm->addButton('', $lang['btn_search'])->attr('type', 'submit');
|
||||
|
||||
$this->addSearchAssistanceElements($searchForm);
|
||||
|
||||
$searchForm->addFieldsetClose();
|
||||
|
||||
Event::createAndTrigger('FORM_SEARCH_OUTPUT', $searchForm);
|
||||
|
||||
return $searchForm->toHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add elements to adjust how the results are sorted
|
||||
*
|
||||
* @param Form $searchForm
|
||||
*/
|
||||
protected function addSortTool(Form $searchForm)
|
||||
{
|
||||
global $INPUT, $lang;
|
||||
|
||||
$options = [
|
||||
'hits' => [
|
||||
'label' => $lang['search_sort_by_hits'],
|
||||
'sort' => '',
|
||||
],
|
||||
'mtime' => [
|
||||
'label' => $lang['search_sort_by_mtime'],
|
||||
'sort' => 'mtime',
|
||||
],
|
||||
];
|
||||
$activeOption = 'hits';
|
||||
|
||||
if ($INPUT->str('srt') === 'mtime') {
|
||||
$activeOption = 'mtime';
|
||||
}
|
||||
|
||||
$searchForm->addTagOpen('div')->addClass('toggle')->attr('aria-haspopup', 'true');
|
||||
// render current
|
||||
$currentWrapper = $searchForm->addTagOpen('div')->addClass('current');
|
||||
if ($activeOption !== 'hits') {
|
||||
$currentWrapper->addClass('changed');
|
||||
}
|
||||
$searchForm->addHTML($options[$activeOption]['label']);
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
// render options list
|
||||
$searchForm->addTagOpen('ul')->attr('aria-expanded', 'false');
|
||||
|
||||
foreach ($options as $key => $option) {
|
||||
$listItem = $searchForm->addTagOpen('li');
|
||||
|
||||
if ($key === $activeOption) {
|
||||
$listItem->addClass('active');
|
||||
$searchForm->addHTML($option['label']);
|
||||
} else {
|
||||
$link = $this->searchState->withSorting($option['sort'])->getSearchLink($option['label']);
|
||||
$searchForm->addHTML($link);
|
||||
}
|
||||
$searchForm->addTagClose('li');
|
||||
}
|
||||
$searchForm->addTagClose('ul');
|
||||
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the query is simple enough to modify its namespace limitations without breaking the rest of the query
|
||||
*
|
||||
* @param array $parsedQuery
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNamespaceAssistanceAvailable(array $parsedQuery) {
|
||||
if (preg_match('/[\(\)\|]/', $parsedQuery['query']) === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the query is simple enough to modify the fragment search behavior without breaking the rest of the query
|
||||
*
|
||||
* @param array $parsedQuery
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFragmentAssistanceAvailable(array $parsedQuery) {
|
||||
if (preg_match('/[\(\)\|]/', $parsedQuery['query']) === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($parsedQuery['phrases'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the elements to be used for search assistance
|
||||
*
|
||||
* @param Form $searchForm
|
||||
*/
|
||||
protected function addSearchAssistanceElements(Form $searchForm)
|
||||
{
|
||||
$searchForm->addTagOpen('div')
|
||||
->addClass('advancedOptions')
|
||||
->attr('style', 'display: none;')
|
||||
->attr('aria-hidden', 'true');
|
||||
|
||||
$this->addFragmentBehaviorLinks($searchForm);
|
||||
$this->addNamespaceSelector($searchForm);
|
||||
$this->addDateSelector($searchForm);
|
||||
$this->addSortTool($searchForm);
|
||||
|
||||
$searchForm->addTagClose('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the elements to adjust the fragment search behavior
|
||||
*
|
||||
* @param Form $searchForm
|
||||
*/
|
||||
protected function addFragmentBehaviorLinks(Form $searchForm)
|
||||
{
|
||||
if (!$this->isFragmentAssistanceAvailable($this->parsedQuery)) {
|
||||
return;
|
||||
}
|
||||
global $lang;
|
||||
|
||||
$options = [
|
||||
'exact' => [
|
||||
'label' => $lang['search_exact_match'],
|
||||
'and' => array_map(function ($term) {
|
||||
return trim($term, '*');
|
||||
}, $this->parsedQuery['and']),
|
||||
'not' => array_map(function ($term) {
|
||||
return trim($term, '*');
|
||||
}, $this->parsedQuery['not']),
|
||||
],
|
||||
'starts' => [
|
||||
'label' => $lang['search_starts_with'],
|
||||
'and' => array_map(function ($term) {
|
||||
return trim($term, '*') . '*';
|
||||
}, $this->parsedQuery['and']),
|
||||
'not' => array_map(function ($term) {
|
||||
return trim($term, '*') . '*';
|
||||
}, $this->parsedQuery['not']),
|
||||
],
|
||||
'ends' => [
|
||||
'label' => $lang['search_ends_with'],
|
||||
'and' => array_map(function ($term) {
|
||||
return '*' . trim($term, '*');
|
||||
}, $this->parsedQuery['and']),
|
||||
'not' => array_map(function ($term) {
|
||||
return '*' . trim($term, '*');
|
||||
}, $this->parsedQuery['not']),
|
||||
],
|
||||
'contains' => [
|
||||
'label' => $lang['search_contains'],
|
||||
'and' => array_map(function ($term) {
|
||||
return '*' . trim($term, '*') . '*';
|
||||
}, $this->parsedQuery['and']),
|
||||
'not' => array_map(function ($term) {
|
||||
return '*' . trim($term, '*') . '*';
|
||||
}, $this->parsedQuery['not']),
|
||||
]
|
||||
];
|
||||
|
||||
// detect current
|
||||
$activeOption = 'custom';
|
||||
foreach ($options as $key => $option) {
|
||||
if ($this->parsedQuery['and'] === $option['and']) {
|
||||
$activeOption = $key;
|
||||
}
|
||||
}
|
||||
if ($activeOption === 'custom') {
|
||||
$options = array_merge(['custom' => [
|
||||
'label' => $lang['search_custom_match'],
|
||||
]], $options);
|
||||
}
|
||||
|
||||
$searchForm->addTagOpen('div')->addClass('toggle')->attr('aria-haspopup', 'true');
|
||||
// render current
|
||||
$currentWrapper = $searchForm->addTagOpen('div')->addClass('current');
|
||||
if ($activeOption !== 'exact') {
|
||||
$currentWrapper->addClass('changed');
|
||||
}
|
||||
$searchForm->addHTML($options[$activeOption]['label']);
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
// render options list
|
||||
$searchForm->addTagOpen('ul')->attr('aria-expanded', 'false');
|
||||
|
||||
foreach ($options as $key => $option) {
|
||||
$listItem = $searchForm->addTagOpen('li');
|
||||
|
||||
if ($key === $activeOption) {
|
||||
$listItem->addClass('active');
|
||||
$searchForm->addHTML($option['label']);
|
||||
} else {
|
||||
$link = $this->searchState
|
||||
->withFragments($option['and'], $option['not'])
|
||||
->getSearchLink($option['label'])
|
||||
;
|
||||
$searchForm->addHTML($link);
|
||||
}
|
||||
$searchForm->addTagClose('li');
|
||||
}
|
||||
$searchForm->addTagClose('ul');
|
||||
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
// render options list
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the elements for the namespace selector
|
||||
*
|
||||
* @param Form $searchForm
|
||||
*/
|
||||
protected function addNamespaceSelector(Form $searchForm)
|
||||
{
|
||||
if (!$this->isNamespaceAssistanceAvailable($this->parsedQuery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $lang;
|
||||
|
||||
$baseNS = empty($this->parsedQuery['ns']) ? '' : $this->parsedQuery['ns'][0];
|
||||
$extraNS = $this->getAdditionalNamespacesFromResults($baseNS);
|
||||
|
||||
$searchForm->addTagOpen('div')->addClass('toggle')->attr('aria-haspopup', 'true');
|
||||
// render current
|
||||
$currentWrapper = $searchForm->addTagOpen('div')->addClass('current');
|
||||
if ($baseNS) {
|
||||
$currentWrapper->addClass('changed');
|
||||
$searchForm->addHTML('@' . $baseNS);
|
||||
} else {
|
||||
$searchForm->addHTML($lang['search_any_ns']);
|
||||
}
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
// render options list
|
||||
$searchForm->addTagOpen('ul')->attr('aria-expanded', 'false');
|
||||
|
||||
$listItem = $searchForm->addTagOpen('li');
|
||||
if ($baseNS) {
|
||||
$listItem->addClass('active');
|
||||
$link = $this->searchState->withNamespace('')->getSearchLink($lang['search_any_ns']);
|
||||
$searchForm->addHTML($link);
|
||||
} else {
|
||||
$searchForm->addHTML($lang['search_any_ns']);
|
||||
}
|
||||
$searchForm->addTagClose('li');
|
||||
|
||||
foreach ($extraNS as $ns => $count) {
|
||||
$listItem = $searchForm->addTagOpen('li');
|
||||
$label = $ns . ($count ? " <bdi>($count)</bdi>" : '');
|
||||
|
||||
if ($ns === $baseNS) {
|
||||
$listItem->addClass('active');
|
||||
$searchForm->addHTML($label);
|
||||
} else {
|
||||
$link = $this->searchState->withNamespace($ns)->getSearchLink($label);
|
||||
$searchForm->addHTML($link);
|
||||
}
|
||||
$searchForm->addTagClose('li');
|
||||
}
|
||||
$searchForm->addTagClose('ul');
|
||||
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the full text results for their top namespaces below the given base namespace
|
||||
*
|
||||
* @param string $baseNS the namespace within which was searched, empty string for root namespace
|
||||
*
|
||||
* @return array an associative array with namespace => #number of found pages, sorted descending
|
||||
*/
|
||||
protected function getAdditionalNamespacesFromResults($baseNS)
|
||||
{
|
||||
$namespaces = [];
|
||||
$baseNSLength = strlen($baseNS);
|
||||
foreach ($this->fullTextResults as $page => $numberOfHits) {
|
||||
$namespace = getNS($page);
|
||||
if (!$namespace) {
|
||||
continue;
|
||||
}
|
||||
if ($namespace === $baseNS) {
|
||||
continue;
|
||||
}
|
||||
$firstColon = strpos((string)$namespace, ':', $baseNSLength + 1) ?: strlen($namespace);
|
||||
$subtopNS = substr($namespace, 0, $firstColon);
|
||||
if (empty($namespaces[$subtopNS])) {
|
||||
$namespaces[$subtopNS] = 0;
|
||||
}
|
||||
$namespaces[$subtopNS] += 1;
|
||||
}
|
||||
ksort($namespaces);
|
||||
arsort($namespaces);
|
||||
return $namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ToDo: custom date input
|
||||
*
|
||||
* @param Form $searchForm
|
||||
*/
|
||||
protected function addDateSelector(Form $searchForm)
|
||||
{
|
||||
global $INPUT, $lang;
|
||||
|
||||
$options = [
|
||||
'any' => [
|
||||
'before' => false,
|
||||
'after' => false,
|
||||
'label' => $lang['search_any_time'],
|
||||
],
|
||||
'week' => [
|
||||
'before' => false,
|
||||
'after' => '1 week ago',
|
||||
'label' => $lang['search_past_7_days'],
|
||||
],
|
||||
'month' => [
|
||||
'before' => false,
|
||||
'after' => '1 month ago',
|
||||
'label' => $lang['search_past_month'],
|
||||
],
|
||||
'year' => [
|
||||
'before' => false,
|
||||
'after' => '1 year ago',
|
||||
'label' => $lang['search_past_year'],
|
||||
],
|
||||
];
|
||||
$activeOption = 'any';
|
||||
foreach ($options as $key => $option) {
|
||||
if ($INPUT->str('min') === $option['after']) {
|
||||
$activeOption = $key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$searchForm->addTagOpen('div')->addClass('toggle')->attr('aria-haspopup', 'true');
|
||||
// render current
|
||||
$currentWrapper = $searchForm->addTagOpen('div')->addClass('current');
|
||||
if ($INPUT->has('max') || $INPUT->has('min')) {
|
||||
$currentWrapper->addClass('changed');
|
||||
}
|
||||
$searchForm->addHTML($options[$activeOption]['label']);
|
||||
$searchForm->addTagClose('div');
|
||||
|
||||
// render options list
|
||||
$searchForm->addTagOpen('ul')->attr('aria-expanded', 'false');
|
||||
|
||||
foreach ($options as $key => $option) {
|
||||
$listItem = $searchForm->addTagOpen('li');
|
||||
|
||||
if ($key === $activeOption) {
|
||||
$listItem->addClass('active');
|
||||
$searchForm->addHTML($option['label']);
|
||||
} else {
|
||||
$link = $this->searchState
|
||||
->withTimeLimitations($option['after'], $option['before'])
|
||||
->getSearchLink($option['label'])
|
||||
;
|
||||
$searchForm->addHTML($link);
|
||||
}
|
||||
$searchForm->addTagClose('li');
|
||||
}
|
||||
$searchForm->addTagClose('ul');
|
||||
|
||||
$searchForm->addTagClose('div');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the intro text for the search page
|
||||
*
|
||||
* @param string $query the search query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getSearchIntroHTML($query)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
$intro = p_locale_xhtml('searchpage');
|
||||
|
||||
$queryPagename = $this->createPagenameFromQuery($this->parsedQuery);
|
||||
$createQueryPageLink = html_wikilink($queryPagename . '?do=edit', $queryPagename);
|
||||
|
||||
$pagecreateinfo = '';
|
||||
if (auth_quickaclcheck($queryPagename) >= AUTH_CREATE) {
|
||||
$pagecreateinfo = sprintf($lang['searchcreatepage'], $createQueryPageLink);
|
||||
}
|
||||
$intro = str_replace(
|
||||
array('@QUERY@', '@SEARCH@', '@CREATEPAGEINFO@'),
|
||||
array(hsc(rawurlencode($query)), hsc($query), $pagecreateinfo),
|
||||
$intro
|
||||
);
|
||||
|
||||
return $intro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pagename based the parsed search query
|
||||
*
|
||||
* @param array $parsedQuery
|
||||
*
|
||||
* @return string pagename constructed from the parsed query
|
||||
*/
|
||||
public function createPagenameFromQuery($parsedQuery)
|
||||
{
|
||||
$cleanedQuery = cleanID($parsedQuery['query']); // already strtolowered
|
||||
if ($cleanedQuery === \dokuwiki\Utf8\PhpString::strtolower($parsedQuery['query'])) {
|
||||
return ':' . $cleanedQuery;
|
||||
}
|
||||
$pagename = '';
|
||||
if (!empty($parsedQuery['ns'])) {
|
||||
$pagename .= ':' . cleanID($parsedQuery['ns'][0]);
|
||||
}
|
||||
$pagename .= ':' . cleanID(implode(' ' , $parsedQuery['highlight']));
|
||||
return $pagename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML for a list of pages with matching pagenames
|
||||
*
|
||||
* @param array $data search results
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPageLookupHTML($data)
|
||||
{
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
global $lang;
|
||||
|
||||
$html = '<div class="search_quickresult">';
|
||||
$html .= '<h2>' . $lang['quickhits'] . ':</h2>';
|
||||
$html .= '<ul class="search_quickhits">';
|
||||
foreach ($data as $id => $title) {
|
||||
$name = null;
|
||||
if (!useHeading('navigation') && $ns = getNS($id)) {
|
||||
$name = shorten(noNS($id), ' (' . $ns . ')', 30);
|
||||
}
|
||||
$link = html_wikilink(':' . $id, $name);
|
||||
$eventData = [
|
||||
'listItemContent' => [$link],
|
||||
'page' => $id,
|
||||
];
|
||||
Event::createAndTrigger('SEARCH_RESULT_PAGELOOKUP', $eventData);
|
||||
$html .= '<li>' . implode('', $eventData['listItemContent']) . '</li>';
|
||||
}
|
||||
$html .= '</ul> ';
|
||||
//clear float (see http://www.complexspiral.com/publications/containing-floats/)
|
||||
$html .= '<div class="clearer"></div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML for fulltext search results or "no results" message
|
||||
*
|
||||
* @param array $data the results of the fulltext search
|
||||
* @param array $highlight the terms to be highlighted in the results
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFulltextResultsHTML($data, $highlight)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (empty($data)) {
|
||||
return '<div class="nothing">' . $lang['nothingfound'] . '</div>';
|
||||
}
|
||||
|
||||
$html = '<div class="search_fulltextresult">';
|
||||
$html .= '<h2>' . $lang['search_fullresults'] . ':</h2>';
|
||||
|
||||
$html .= '<dl class="search_results">';
|
||||
$num = 0;
|
||||
$position = 0;
|
||||
|
||||
foreach ($data as $id => $cnt) {
|
||||
$position += 1;
|
||||
$resultLink = html_wikilink(':' . $id, null, $highlight);
|
||||
|
||||
$resultHeader = [$resultLink];
|
||||
|
||||
|
||||
$restrictQueryToNSLink = $this->restrictQueryToNSLink(getNS($id));
|
||||
if ($restrictQueryToNSLink) {
|
||||
$resultHeader[] = $restrictQueryToNSLink;
|
||||
}
|
||||
|
||||
$resultBody = [];
|
||||
$mtime = filemtime(wikiFN($id));
|
||||
$lastMod = '<span class="lastmod">' . $lang['lastmod'] . '</span> ';
|
||||
$lastMod .= '<time datetime="' . date_iso8601($mtime) . '" title="' . dformat($mtime) . '">' .
|
||||
dformat($mtime, '%f') .
|
||||
'</time>';
|
||||
$resultBody['meta'] = $lastMod;
|
||||
if ($cnt !== 0) {
|
||||
$num++;
|
||||
$hits = '<span class="hits">' . $cnt . ' ' . $lang['hits'] . '</span>, ';
|
||||
$resultBody['meta'] = $hits . $resultBody['meta'];
|
||||
if ($num <= FT_SNIPPET_NUMBER) { // create snippets for the first number of matches only
|
||||
$resultBody['snippet'] = ft_snippet($id, $highlight);
|
||||
}
|
||||
}
|
||||
|
||||
$eventData = [
|
||||
'resultHeader' => $resultHeader,
|
||||
'resultBody' => $resultBody,
|
||||
'page' => $id,
|
||||
'position' => $position,
|
||||
];
|
||||
Event::createAndTrigger('SEARCH_RESULT_FULLPAGE', $eventData);
|
||||
$html .= '<div class="search_fullpage_result">';
|
||||
$html .= '<dt>' . implode(' ', $eventData['resultHeader']) . '</dt>';
|
||||
foreach ($eventData['resultBody'] as $class => $htmlContent) {
|
||||
$html .= "<dd class=\"$class\">$htmlContent</dd>";
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</dl>';
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a link to restrict the current query to a namespace
|
||||
*
|
||||
* @param false|string $ns the namespace to which to restrict the query
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected function restrictQueryToNSLink($ns)
|
||||
{
|
||||
if (!$ns) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->isNamespaceAssistanceAvailable($this->parsedQuery)) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($this->parsedQuery['ns']) && $this->parsedQuery['ns'][0] === $ns) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = '@' . $ns;
|
||||
return $this->searchState->withNamespace($ns)->getSearchLink($name);
|
||||
}
|
||||
}
|
141
content/inc/Ui/SearchState.php
Normal file
141
content/inc/Ui/SearchState.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
class SearchState
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $parsedQuery = [];
|
||||
|
||||
/**
|
||||
* SearchState constructor.
|
||||
*
|
||||
* @param array $parsedQuery
|
||||
*/
|
||||
public function __construct(array $parsedQuery)
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
$this->parsedQuery = $parsedQuery;
|
||||
if (!isset($parsedQuery['after'])) {
|
||||
$this->parsedQuery['after'] = $INPUT->str('min');
|
||||
}
|
||||
if (!isset($parsedQuery['before'])) {
|
||||
$this->parsedQuery['before'] = $INPUT->str('max');
|
||||
}
|
||||
if (!isset($parsedQuery['sort'])) {
|
||||
$this->parsedQuery['sort'] = $INPUT->str('srt');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a search state for the current search limited to a new namespace
|
||||
*
|
||||
* @param string $ns the namespace to which to limit the search, falsy to remove the limitation
|
||||
* @param array $notns
|
||||
*
|
||||
* @return SearchState
|
||||
*/
|
||||
public function withNamespace($ns, array $notns = [])
|
||||
{
|
||||
$parsedQuery = $this->parsedQuery;
|
||||
$parsedQuery['ns'] = $ns ? [$ns] : [];
|
||||
$parsedQuery['notns'] = $notns;
|
||||
|
||||
return new SearchState($parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a search state for the current search with new search fragments and optionally phrases
|
||||
*
|
||||
* @param array $and
|
||||
* @param array $not
|
||||
* @param array $phrases
|
||||
*
|
||||
* @return SearchState
|
||||
*/
|
||||
public function withFragments(array $and, array $not, array $phrases = [])
|
||||
{
|
||||
$parsedQuery = $this->parsedQuery;
|
||||
$parsedQuery['and'] = $and;
|
||||
$parsedQuery['not'] = $not;
|
||||
$parsedQuery['phrases'] = $phrases;
|
||||
|
||||
return new SearchState($parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a search state for the current search with with adjusted time limitations
|
||||
*
|
||||
* @param $after
|
||||
* @param $before
|
||||
*
|
||||
* @return SearchState
|
||||
*/
|
||||
public function withTimeLimitations($after, $before)
|
||||
{
|
||||
$parsedQuery = $this->parsedQuery;
|
||||
$parsedQuery['after'] = $after;
|
||||
$parsedQuery['before'] = $before;
|
||||
|
||||
return new SearchState($parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a search state for the current search with adjusted sort preference
|
||||
*
|
||||
* @param $sort
|
||||
*
|
||||
* @return SearchState
|
||||
*/
|
||||
public function withSorting($sort)
|
||||
{
|
||||
$parsedQuery = $this->parsedQuery;
|
||||
$parsedQuery['sort'] = $sort;
|
||||
|
||||
return new SearchState($parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link that represents the current search state
|
||||
*
|
||||
* Note that this represents only a simplified version of the search state.
|
||||
* Grouping with braces and "OR" conditions are not supported.
|
||||
*
|
||||
* @param $label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSearchLink($label)
|
||||
{
|
||||
global $ID, $conf;
|
||||
$parsedQuery = $this->parsedQuery;
|
||||
|
||||
$tagAttributes = [
|
||||
'target' => $conf['target']['wiki'],
|
||||
];
|
||||
|
||||
$newQuery = ft_queryUnparser_simple(
|
||||
$parsedQuery['and'],
|
||||
$parsedQuery['not'],
|
||||
$parsedQuery['phrases'],
|
||||
$parsedQuery['ns'],
|
||||
$parsedQuery['notns']
|
||||
);
|
||||
$hrefAttributes = ['do' => 'search', 'sf' => '1', 'q' => $newQuery];
|
||||
if ($parsedQuery['after']) {
|
||||
$hrefAttributes['min'] = $parsedQuery['after'];
|
||||
}
|
||||
if ($parsedQuery['before']) {
|
||||
$hrefAttributes['max'] = $parsedQuery['before'];
|
||||
}
|
||||
if ($parsedQuery['sort']) {
|
||||
$hrefAttributes['srt'] = $parsedQuery['sort'];
|
||||
}
|
||||
|
||||
$href = wl($ID, $hrefAttributes, false, '&');
|
||||
return "<a href='$href' " . buildAttributes($tagAttributes, true) . ">$label</a>";
|
||||
}
|
||||
}
|
20
content/inc/Ui/Ui.php
Normal file
20
content/inc/Ui/Ui.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Ui
|
||||
*
|
||||
* Abstract base class for all DokuWiki screens
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
abstract class Ui {
|
||||
|
||||
/**
|
||||
* Display the UI element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function show();
|
||||
|
||||
}
|
Reference in New Issue
Block a user