* @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 '

' . $this->getLang('test_patterns_help') . '

'; echo '
'; echo ' '; echo ''; 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 ''; 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 ''; echo ''; echo ''; echo ''; echo ''; foreach ($matchedPages as $match) { echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . $this->getLang('page_id') . '' . $this->getLang('match_target') . '' . $this->getLang('matched_pattern') . '
' . hsc($match['id']) . '' . hsc($match['target']) . '' . hsc($match['pattern']) . '
'; } echo '
'; } }