* @copyright 2025 Johann Duscher
*/
use dokuwiki\Extension\AdminPlugin;
// Protect against direct call
if (!defined('DOKU_INC')) die();
/**
* Class admin_plugin_deletepageguard
*
* Provides an admin interface for validating Delete Page Guard patterns
* and offering configuration guidance to administrators.
*/
class admin_plugin_deletepageguard extends AdminPlugin {
/**
* Cached instance of the action plugin
* @var action_plugin_deletepageguard|null
*/
private $actionPlugin = null;
/**
* Get the action plugin instance (cached)
* @return action_plugin_deletepageguard|null
*/
private function getActionPlugin() {
if ($this->actionPlugin === null) {
$this->actionPlugin = plugin_load('action', 'deletepageguard');
}
return $this->actionPlugin;
}
/**
* Return sort order for position in admin menu
* @return int
*/
public function getMenuSort() {
return 200;
}
/**
* Return the text to display in the admin menu
* @return string
*/
public function getMenuText($language) {
return $this->getLang('menu');
}
/**
* Return true if access to this admin plugin is allowed
* @return bool
*/
public function forAdminOnly() {
return true;
}
/**
* Handle user request
* @return void
*/
public function handle() {
// Nothing to handle - validation is done in html() method
}
/**
* Render HTML output
* @return void
*/
public function html() {
echo '
' . $this->getLang('admin_title') . '
';
echo '';
// Determine which patterns to show - use POST data if available, otherwise config
$patterns = $_POST['test_patterns'] ?? $this->getConf('patterns');
// Show validation results if "Validate" button was clicked
if (isset($_POST['validate_patterns'])) {
echo '
' . $this->getLang('validation_results_title') . '
';
$this->showPatternValidation($patterns);
}
// Show matching pages if "Show Matches" button was clicked
elseif (isset($_POST['show_matches'])) {
echo '
' . $this->getLang('validation_results_title') . '
';
$this->showPatternValidation($patterns);
echo '
' . $this->getLang('matching_pages_title') . '
';
$this->showMatchingPages($patterns);
}
// Initial load - just show validation
else {
$this->showPatternValidation($patterns);
}
// Add validation form
echo '
' . $this->getLang('test_patterns_title') . '
';
echo '
';
echo '
';
}
/**
* Display pattern validation results
* @param string $patterns The patterns to validate
* @return void
*/
private function showPatternValidation($patterns) {
if (empty(trim($patterns))) {
echo '' . $this->getLang('no_patterns') . '
';
return;
}
$lines = preg_split('/\R+/', $patterns, -1, PREG_SPLIT_NO_EMPTY);
$hasErrors = false;
$validCount = 0;
echo '';
echo '
' . $this->getLang('validation_results') . '
';
echo '
';
foreach ($lines as $i => $line) {
$pattern = trim($line);
if ($pattern === '') continue;
$lineNum = $i + 1;
$status = $this->validateSinglePattern($pattern);
if ($status === true) {
echo '- ✓ ';
echo 'Line ' . $lineNum . ':
' . hsc($pattern) . ' ';
$validCount++;
} else {
echo '- ✗ ';
echo 'Line ' . $lineNum . ':
' . hsc($pattern) . '
';
echo ' ' . hsc($status) . ' ';
$hasErrors = true;
}
}
echo '
';
if (!$hasErrors && $validCount > 0) {
echo '
' . sprintf($this->getLang('all_patterns_valid'), $validCount) . '
';
} elseif ($hasErrors) {
echo '
' . $this->getLang('some_patterns_invalid') . '
';
}
echo '
';
}
/**
* Validate a single pattern by delegating to the action plugin's validator.
* This ensures consistent validation logic between admin UI and runtime checks.
*
* @param string $pattern The pattern to validate
* @return string|true True if valid, error message if invalid
*/
private function validateSinglePattern($pattern) {
// Load the action plugin to use its centralized validation logic
$actionPlugin = $this->getActionPlugin();
if (!$actionPlugin) {
return 'Error: Could not load validation service';
}
// Use the action plugin's validateRegexPattern method (without line number)
$result = $actionPlugin->validateRegexPattern($pattern, 0);
// The action plugin returns true for valid, string for invalid
// We need to strip the "Line 0: " prefix if present
if (is_string($result)) {
$result = preg_replace('/^Line 0: /', '', $result);
}
return $result;
}
/**
* Show all wiki pages that match the given patterns
* @param string $patterns The patterns to test
* @return void
*/
private function showMatchingPages($patterns) {
// Load action plugin for matching logic
$actionPlugin = $this->getActionPlugin();
if (!$actionPlugin) {
echo 'Error: Could not load action plugin
';
return;
}
// Parse patterns
$lines = preg_split('/\R+/', $patterns, -1, PREG_SPLIT_NO_EMPTY);
$validPatterns = [];
foreach ($lines as $line) {
$pattern = trim($line);
if ($pattern === '') continue;
// Only use valid patterns
if ($actionPlugin->validateRegexPattern($pattern, 0) === true) {
$validPatterns[] = $pattern;
}
}
if (empty($validPatterns)) {
echo '' . $this->getLang('no_valid_patterns') . '
';
return;
}
// Get all pages using DokuWiki's search function
global $conf;
$allPages = [];
// DokuWiki's search expects to search in the pages directory
$pagesDir = $conf['datadir'] . '/pages';
search($allPages, $pagesDir, 'search_allpages', []);
// Fallback: use indexer if search returns nothing
if (empty($allPages)) {
require_once(DOKU_INC . 'inc/indexer.php');
$indexer = idx_get_indexer();
$pagesList = $indexer->getPages();
// Convert simple page list to expected format
if (!empty($pagesList)) {
$allPages = [];
foreach ($pagesList as $pageId) {
$allPages[] = ['id' => $pageId];
}
}
}
if (empty($allPages)) {
echo '' . $this->getLang('no_pages_found') . '
';
return;
}
// Test each page against patterns
$matchedPages = [];
$testedCount = 0;
foreach ($allPages as $page) {
$pageId = $page['id'];
$matchTarget = $actionPlugin->getMatchTarget($pageId);
$testedCount++;
foreach ($validPatterns as $pattern) {
if ($actionPlugin->matchesPattern($pattern, $matchTarget)) {
$matchedPages[] = [
'id' => $pageId,
'target' => $matchTarget,
'pattern' => $pattern
];
break; // Only list each page once
}
}
}
// Display results
echo '';
if (empty($matchedPages)) {
echo '
' . sprintf($this->getLang('no_matching_pages'), count($allPages)) . '
';
} else {
echo '
' . sprintf($this->getLang('found_matching_pages'), count($matchedPages), count($allPages)) . '
';
echo '
';
echo '';
echo '| ' . $this->getLang('page_id') . ' | ';
echo '' . $this->getLang('match_target') . ' | ';
echo '' . $this->getLang('matched_pattern') . ' | ';
echo '
';
echo '';
foreach ($matchedPages as $match) {
echo '';
echo '| ' . hsc($match['id']) . ' | ';
echo '' . hsc($match['target']) . ' | ';
echo '' . hsc($match['pattern']) . ' | ';
echo '
';
}
echo '
';
}
echo '
';
}
}