1*59036814SCostin Stroie<?php 2*59036814SCostin Stroie/** 3*59036814SCostin Stroie * DokuWiki Plugin dokullm (Action Component) 4*59036814SCostin Stroie * 5*59036814SCostin Stroie * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6*59036814SCostin Stroie * @author Costin Stroie <costinstroie@eridu.eu.org> 7*59036814SCostin Stroie */ 8*59036814SCostin Stroie 9*59036814SCostin Stroie// must be run within Dokuwiki 10*59036814SCostin Stroieif (!defined('DOKU_INC')) { 11*59036814SCostin Stroie die(); 12*59036814SCostin Stroie} 13*59036814SCostin Stroie 14*59036814SCostin Stroie/** 15*59036814SCostin Stroie * Main action component for the dokullm plugin 16*59036814SCostin Stroie * 17*59036814SCostin Stroie * This class handles: 18*59036814SCostin Stroie * - Registering event handlers for page rendering and AJAX calls 19*59036814SCostin Stroie * - Adding JavaScript to edit pages 20*59036814SCostin Stroie * - Processing AJAX requests from the frontend 21*59036814SCostin Stroie * - Handling page template loading with metadata support 22*59036814SCostin Stroie * - Adding copy page button to page tools 23*59036814SCostin Stroie * 24*59036814SCostin Stroie * The plugin provides integration with LLM APIs for text processing 25*59036814SCostin Stroie * operations directly within the DokuWiki editor. 26*59036814SCostin Stroie * 27*59036814SCostin Stroie * Configuration options: 28*59036814SCostin Stroie * - api_url: The LLM API endpoint URL 29*59036814SCostin Stroie * - api_key: Authentication key for the API (optional) 30*59036814SCostin Stroie * - model: The model identifier to use for requests 31*59036814SCostin Stroie * - timeout: Request timeout in seconds 32*59036814SCostin Stroie * - language: Language code for prompt templates 33*59036814SCostin Stroie * - temperature: Temperature setting for response randomness (0.0-1.0) 34*59036814SCostin Stroie * - top_p: Top-p (nucleus sampling) setting (0.0-1.0) 35*59036814SCostin Stroie * - top_k: Top-k setting (integer >= 1) 36*59036814SCostin Stroie * - min_p: Minimum probability threshold (0.0-1.0) 37*59036814SCostin Stroie * - think: Whether to enable thinking in LLM responses (boolean) 38*59036814SCostin Stroie * - show_copy_button: Whether to show the copy page button (boolean) 39*59036814SCostin Stroie * - replace_id: Whether to replace template ID when copying (boolean) 40*59036814SCostin Stroie */ 41*59036814SCostin Stroieclass action_plugin_dokullm extends DokuWiki_Action_Plugin 42*59036814SCostin Stroie{ 43*59036814SCostin Stroie /** 44*59036814SCostin Stroie * Register the event handlers for this plugin 45*59036814SCostin Stroie * 46*59036814SCostin Stroie * Hooks into: 47*59036814SCostin Stroie * - TPL_METAHEADER_OUTPUT: To add JavaScript to edit pages 48*59036814SCostin Stroie * - AJAX_CALL_UNKNOWN: To handle plugin-specific AJAX requests 49*59036814SCostin Stroie * 50*59036814SCostin Stroie * @param Doku_Event_Handler $controller The event handler controller 51*59036814SCostin Stroie */ 52*59036814SCostin Stroie public function register(Doku_Event_Handler $controller) 53*59036814SCostin Stroie { 54*59036814SCostin Stroie $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleMetaHeaders'); 55*59036814SCostin Stroie $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax'); 56*59036814SCostin Stroie $controller->register_hook('COMMON_PAGETPL_LOAD', 'BEFORE', $this, 'handleTemplate'); 57*59036814SCostin Stroie $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'addCopyPageButton', array()); 58*59036814SCostin Stroie $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'handlePageSave'); 59*59036814SCostin Stroie } 60*59036814SCostin Stroie 61*59036814SCostin Stroie /** 62*59036814SCostin Stroie * Add JavaScript to the page header for edit pages 63*59036814SCostin Stroie * 64*59036814SCostin Stroie * This method checks if we're on an edit or preview page and adds 65*59036814SCostin Stroie * the plugin's JavaScript file to the page header. 66*59036814SCostin Stroie * 67*59036814SCostin Stroie * @param Doku_Event $event The event object 68*59036814SCostin Stroie * @param mixed $param Additional parameters 69*59036814SCostin Stroie */ 70*59036814SCostin Stroie public function handleMetaHeaders(Doku_Event $event, $param) 71*59036814SCostin Stroie { 72*59036814SCostin Stroie global $INFO; 73*59036814SCostin Stroie 74*59036814SCostin Stroie // Only add JS to edit pages 75*59036814SCostin Stroie if ($INFO['act'] == 'edit' || $INFO['act'] == 'preview') { 76*59036814SCostin Stroie $event->data['script'][] = array( 77*59036814SCostin Stroie 'type' => 'text/javascript', 78*59036814SCostin Stroie 'src' => DOKU_BASE . 'lib/plugins/dokullm/script.js', 79*59036814SCostin Stroie '_data' => 'dokullm' 80*59036814SCostin Stroie ); 81*59036814SCostin Stroie } 82*59036814SCostin Stroie } 83*59036814SCostin Stroie 84*59036814SCostin Stroie /** 85*59036814SCostin Stroie * Handle AJAX requests for the plugin 86*59036814SCostin Stroie * 87*59036814SCostin Stroie * Processes AJAX calls with the identifier 'plugin_dokullm' and 88*59036814SCostin Stroie * routes them to the appropriate text processing method. 89*59036814SCostin Stroie * 90*59036814SCostin Stroie * @param Doku_Event $event The event object 91*59036814SCostin Stroie * @param mixed $param Additional parameters 92*59036814SCostin Stroie */ 93*59036814SCostin Stroie public function handleAjax(Doku_Event $event, $param) 94*59036814SCostin Stroie { 95*59036814SCostin Stroie if ($event->data !== 'plugin_dokullm') { 96*59036814SCostin Stroie return; 97*59036814SCostin Stroie } 98*59036814SCostin Stroie 99*59036814SCostin Stroie $event->stopPropagation(); 100*59036814SCostin Stroie $event->preventDefault(); 101*59036814SCostin Stroie 102*59036814SCostin Stroie // Handle the AJAX request 103*59036814SCostin Stroie $this->processRequest(); 104*59036814SCostin Stroie } 105*59036814SCostin Stroie 106*59036814SCostin Stroie /** 107*59036814SCostin Stroie * Process the AJAX request and return JSON response 108*59036814SCostin Stroie * 109*59036814SCostin Stroie * Extracts action, text, prompt, metadata, and template parameters from the request, 110*59036814SCostin Stroie * validates the input, and calls the appropriate processing method. 111*59036814SCostin Stroie * Returns JSON encoded result or error. 112*59036814SCostin Stroie * 113*59036814SCostin Stroie * @return void 114*59036814SCostin Stroie */ 115*59036814SCostin Stroie private function processRequest() 116*59036814SCostin Stroie { 117*59036814SCostin Stroie global $INPUT; 118*59036814SCostin Stroie 119*59036814SCostin Stroie // Get form data 120*59036814SCostin Stroie $action = $INPUT->str('action'); 121*59036814SCostin Stroie $text = $INPUT->str('text'); 122*59036814SCostin Stroie $prompt = $INPUT->str('prompt', ''); 123*59036814SCostin Stroie $template = $INPUT->str('template', ''); 124*59036814SCostin Stroie $examples = $INPUT->str('examples', ''); 125*59036814SCostin Stroie $previous = $INPUT->str('previous', ''); 126*59036814SCostin Stroie 127*59036814SCostin Stroie // Parse examples - split by newline and filter out empty lines 128*59036814SCostin Stroie $examplesList = array_filter(array_map('trim', explode("\n", $examples))); 129*59036814SCostin Stroie 130*59036814SCostin Stroie // Create metadata object with prompt, template, examples, and previous 131*59036814SCostin Stroie $metadata = [ 132*59036814SCostin Stroie 'prompt' => $prompt, 133*59036814SCostin Stroie 'template' => $template, 134*59036814SCostin Stroie 'examples' => $examplesList, 135*59036814SCostin Stroie 'previous' => $previous 136*59036814SCostin Stroie ]; 137*59036814SCostin Stroie 138*59036814SCostin Stroie // Handle the special case of get_actions action 139*59036814SCostin Stroie if ($action === 'get_actions') { 140*59036814SCostin Stroie try { 141*59036814SCostin Stroie $actions = $this->getActions(); 142*59036814SCostin Stroie echo json_encode(['result' => $actions]); 143*59036814SCostin Stroie } catch (Exception $e) { 144*59036814SCostin Stroie http_status(500); 145*59036814SCostin Stroie echo json_encode(['error' => $e->getMessage()]); 146*59036814SCostin Stroie } 147*59036814SCostin Stroie return; 148*59036814SCostin Stroie } 149*59036814SCostin Stroie 150*59036814SCostin Stroie // Handle the special case of get_template action 151*59036814SCostin Stroie if ($action === 'get_template') { 152*59036814SCostin Stroie try { 153*59036814SCostin Stroie $templateId = $template; 154*59036814SCostin Stroie $templateContent = $this->getPageContent($templateId); 155*59036814SCostin Stroie if ($templateContent === false) { 156*59036814SCostin Stroie throw new Exception('Template not found: ' . $templateId); 157*59036814SCostin Stroie } 158*59036814SCostin Stroie echo json_encode(['result' => ['content' => $templateContent]]); 159*59036814SCostin Stroie } catch (Exception $e) { 160*59036814SCostin Stroie http_status(500); 161*59036814SCostin Stroie echo json_encode(['error' => $e->getMessage()]); 162*59036814SCostin Stroie } 163*59036814SCostin Stroie return; 164*59036814SCostin Stroie } 165*59036814SCostin Stroie 166*59036814SCostin Stroie // Handle the special case of find_template action 167*59036814SCostin Stroie if ($action === 'find_template') { 168*59036814SCostin Stroie try { 169*59036814SCostin Stroie $searchText = $INPUT->str('text'); 170*59036814SCostin Stroie $template = $this->findTemplate($searchText); 171*59036814SCostin Stroie if (!empty($template)) { 172*59036814SCostin Stroie echo json_encode(['result' => ['template' => $template[0]]]); 173*59036814SCostin Stroie } else { 174*59036814SCostin Stroie echo json_encode(['result' => ['template' => null]]); 175*59036814SCostin Stroie } 176*59036814SCostin Stroie } catch (Exception $e) { 177*59036814SCostin Stroie http_status(500); 178*59036814SCostin Stroie echo json_encode(['error' => $e->getMessage()]); 179*59036814SCostin Stroie } 180*59036814SCostin Stroie return; 181*59036814SCostin Stroie } 182*59036814SCostin Stroie 183*59036814SCostin Stroie // Validate input 184*59036814SCostin Stroie if (empty($text)) { 185*59036814SCostin Stroie http_status(400); 186*59036814SCostin Stroie echo json_encode(['error' => 'No text provided']); 187*59036814SCostin Stroie return; 188*59036814SCostin Stroie } 189*59036814SCostin Stroie 190*59036814SCostin Stroie 191*59036814SCostin Stroie $client = new \dokuwiki\plugin\dokullm\LlmClient(); 192*59036814SCostin Stroie try { 193*59036814SCostin Stroie switch ($action) { 194*59036814SCostin Stroie case 'create_DISABLED': 195*59036814SCostin Stroie $result = $client->createReport($text, $metadata); 196*59036814SCostin Stroie case 'compare_DISABLED': 197*59036814SCostin Stroie $result = $client->compareText($text, $metadata); 198*59036814SCostin Stroie case 'custom': 199*59036814SCostin Stroie $result = $client->processCustomPrompt($text, $metadata); 200*59036814SCostin Stroie default: 201*59036814SCostin Stroie $result = $client->process($action, $text, $metadata); 202*59036814SCostin Stroie } 203*59036814SCostin Stroie echo json_encode(['result' => $result]); 204*59036814SCostin Stroie } catch (Exception $e) { 205*59036814SCostin Stroie http_status(500); 206*59036814SCostin Stroie echo json_encode(['error' => $e->getMessage()]); 207*59036814SCostin Stroie } 208*59036814SCostin Stroie } 209*59036814SCostin Stroie 210*59036814SCostin Stroie /** 211*59036814SCostin Stroie * Get action definitions from the DokuWiki table at dokullm:prompts 212*59036814SCostin Stroie * 213*59036814SCostin Stroie * Parses the table containing action definitions with columns: 214*59036814SCostin Stroie * ID, Label, Icon, Action 215*59036814SCostin Stroie * 216*59036814SCostin Stroie * Stops parsing after the first table ends to avoid processing 217*59036814SCostin Stroie * additional tables with disabled or work-in-progress commands. 218*59036814SCostin Stroie * 219*59036814SCostin Stroie * @return array Array of action definitions 220*59036814SCostin Stroie */ 221*59036814SCostin Stroie private function getActions() 222*59036814SCostin Stroie { 223*59036814SCostin Stroie // Get the content of the prompts page 224*59036814SCostin Stroie $content = $this->getPageContent('dokullm:prompts'); 225*59036814SCostin Stroie 226*59036814SCostin Stroie if ($content === false) { 227*59036814SCostin Stroie // Return empty list if page doesn't exist 228*59036814SCostin Stroie return []; 229*59036814SCostin Stroie } 230*59036814SCostin Stroie 231*59036814SCostin Stroie // Parse the table from the page content 232*59036814SCostin Stroie $actions = []; 233*59036814SCostin Stroie $lines = explode("\n", $content); 234*59036814SCostin Stroie $inTable = false; 235*59036814SCostin Stroie 236*59036814SCostin Stroie foreach ($lines as $line) { 237*59036814SCostin Stroie // Check if this is a table row 238*59036814SCostin Stroie if (preg_match('/^\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|$/', $line, $matches)) { 239*59036814SCostin Stroie $inTable = true; 240*59036814SCostin Stroie 241*59036814SCostin Stroie // Skip header row 242*59036814SCostin Stroie if (trim($matches[1]) === 'ID' || trim($matches[1]) === 'id') { 243*59036814SCostin Stroie continue; 244*59036814SCostin Stroie } 245*59036814SCostin Stroie 246*59036814SCostin Stroie $actions[] = [ 247*59036814SCostin Stroie 'id' => trim($matches[1]), 248*59036814SCostin Stroie 'label' => trim($matches[2]), 249*59036814SCostin Stroie 'description' => trim($matches[3]), 250*59036814SCostin Stroie 'icon' => trim($matches[4]), 251*59036814SCostin Stroie 'result' => trim($matches[5]) 252*59036814SCostin Stroie ]; 253*59036814SCostin Stroie } else if ($inTable) { 254*59036814SCostin Stroie // We've exited the table, so stop parsing 255*59036814SCostin Stroie break; 256*59036814SCostin Stroie } 257*59036814SCostin Stroie } 258*59036814SCostin Stroie 259*59036814SCostin Stroie return $actions; 260*59036814SCostin Stroie } 261*59036814SCostin Stroie 262*59036814SCostin Stroie /** 263*59036814SCostin Stroie * Get the content of a DokuWiki page 264*59036814SCostin Stroie * 265*59036814SCostin Stroie * Retrieves the raw content of a DokuWiki page by its ID. 266*59036814SCostin Stroie * Used for loading template and example page content for context. 267*59036814SCostin Stroie * 268*59036814SCostin Stroie * @param string $pageId The page ID to retrieve 269*59036814SCostin Stroie * @return string|false The page content or false if not found/readable 270*59036814SCostin Stroie */ 271*59036814SCostin Stroie private function getPageContent($pageId) 272*59036814SCostin Stroie { 273*59036814SCostin Stroie // Convert page ID to file path 274*59036814SCostin Stroie $pageFile = wikiFN($pageId); 275*59036814SCostin Stroie 276*59036814SCostin Stroie // Check if file exists and is readable 277*59036814SCostin Stroie if (file_exists($pageFile) && is_readable($pageFile)) { 278*59036814SCostin Stroie return file_get_contents($pageFile); 279*59036814SCostin Stroie } 280*59036814SCostin Stroie 281*59036814SCostin Stroie return false; 282*59036814SCostin Stroie } 283*59036814SCostin Stroie 284*59036814SCostin Stroie 285*59036814SCostin Stroie /** 286*59036814SCostin Stroie * Find an appropriate template based on the provided text 287*59036814SCostin Stroie * 288*59036814SCostin Stroie * Uses ChromaDB to search for the most relevant template based on the content. 289*59036814SCostin Stroie * 290*59036814SCostin Stroie * @param string $text The text to use for finding a template 291*59036814SCostin Stroie * @return array The template ID array or empty array if none found 292*59036814SCostin Stroie * @throws Exception If an error occurs during the search 293*59036814SCostin Stroie */ 294*59036814SCostin Stroie private function findTemplate($text) { 295*59036814SCostin Stroie try { 296*59036814SCostin Stroie // Get ChromaDB client through the LLM client 297*59036814SCostin Stroie $client = new \dokuwiki\plugin\dokullm\LlmClient(); 298*59036814SCostin Stroie 299*59036814SCostin Stroie // Query ChromaDB for the most relevant template 300*59036814SCostin Stroie $template = $client->queryChromaDBTemplate($text); 301*59036814SCostin Stroie 302*59036814SCostin Stroie return $template; 303*59036814SCostin Stroie } catch (Exception $e) { 304*59036814SCostin Stroie throw new Exception('Error finding template: ' . $e->getMessage()); 305*59036814SCostin Stroie } 306*59036814SCostin Stroie } 307*59036814SCostin Stroie 308*59036814SCostin Stroie 309*59036814SCostin Stroie /** 310*59036814SCostin Stroie * Handle page save event and send page to ChromaDB 311*59036814SCostin Stroie * 312*59036814SCostin Stroie * This method is triggered after a page is saved and sends the page content 313*59036814SCostin Stroie * to ChromaDB for indexing. 314*59036814SCostin Stroie * 315*59036814SCostin Stroie * @param Doku_Event $event The event object 316*59036814SCostin Stroie * @param mixed $param Additional parameters 317*59036814SCostin Stroie */ 318*59036814SCostin Stroie public function handlePageSave(Doku_Event $event, $param) 319*59036814SCostin Stroie { 320*59036814SCostin Stroie global $ID; 321*59036814SCostin Stroie 322*59036814SCostin Stroie // Only process if we have a valid page ID 323*59036814SCostin Stroie if (empty($ID)) { 324*59036814SCostin Stroie return; 325*59036814SCostin Stroie } 326*59036814SCostin Stroie 327*59036814SCostin Stroie // Get the page content 328*59036814SCostin Stroie $content = rawWiki($ID); 329*59036814SCostin Stroie 330*59036814SCostin Stroie // Skip empty pages 331*59036814SCostin Stroie if (empty($content)) { 332*59036814SCostin Stroie return; 333*59036814SCostin Stroie } 334*59036814SCostin Stroie 335*59036814SCostin Stroie try { 336*59036814SCostin Stroie // Send page to ChromaDB 337*59036814SCostin Stroie $this->sendPageToChromaDB($ID, $content); 338*59036814SCostin Stroie } catch (Exception $e) { 339*59036814SCostin Stroie // Log error but don't stop execution 340*59036814SCostin Stroie \dokuwiki\Logger::error('dokullm: Error sending page to ChromaDB: ' . $e->getMessage()); 341*59036814SCostin Stroie } 342*59036814SCostin Stroie } 343*59036814SCostin Stroie 344*59036814SCostin Stroie 345*59036814SCostin Stroie /** 346*59036814SCostin Stroie * Send page content to ChromaDB 347*59036814SCostin Stroie * 348*59036814SCostin Stroie * @param string $pageId The page ID 349*59036814SCostin Stroie * @param string $content The page content 350*59036814SCostin Stroie * @return void 351*59036814SCostin Stroie */ 352*59036814SCostin Stroie private function sendPageToChromaDB($pageId, $content) 353*59036814SCostin Stroie { 354*59036814SCostin Stroie // Convert page ID to file path format for ChromaDB 355*59036814SCostin Stroie $filePath = wikiFN($pageId); 356*59036814SCostin Stroie 357*59036814SCostin Stroie try { 358*59036814SCostin Stroie // Use the existing ChromaDB client to process the file 359*59036814SCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient( 360*59036814SCostin Stroie CHROMA_HOST, 361*59036814SCostin Stroie CHROMA_PORT, 362*59036814SCostin Stroie CHROMA_TENANT, 363*59036814SCostin Stroie CHROMA_DATABASE, 364*59036814SCostin Stroie OLLAMA_HOST, 365*59036814SCostin Stroie OLLAMA_PORT, 366*59036814SCostin Stroie OLLAMA_EMBEDDINGS_MODEL 367*59036814SCostin Stroie ); 368*59036814SCostin Stroie 369*59036814SCostin Stroie // Use the first part of the document ID as collection name, fallback to 'documents' 370*59036814SCostin Stroie $idParts = explode(':', $pageId); 371*59036814SCostin Stroie $collectionName = isset($idParts[0]) && !empty($idParts[0]) ? $idParts[0] : 'documents'; 372*59036814SCostin Stroie 373*59036814SCostin Stroie // Process the file directly 374*59036814SCostin Stroie $result = $chroma->processSingleFile($filePath, $collectionName, false); 375*59036814SCostin Stroie 376*59036814SCostin Stroie // Log success or failure 377*59036814SCostin Stroie if ($result['status'] === 'success') { 378*59036814SCostin Stroie \dokuwiki\Logger::debug('dokullm: Successfully sent page to ChromaDB: ' . $pageId); 379*59036814SCostin Stroie } else if ($result['status'] === 'skipped') { 380*59036814SCostin Stroie \dokuwiki\Logger::debug('dokullm: Skipped sending page to ChromaDB: ' . $pageId . ' - ' . $result['message']); 381*59036814SCostin Stroie } else { 382*59036814SCostin Stroie \dokuwiki\Logger::error('dokullm: Error sending page to ChromaDB: ' . $pageId . ' - ' . $result['message']); 383*59036814SCostin Stroie } 384*59036814SCostin Stroie } catch (Exception $e) { 385*59036814SCostin Stroie throw $e; 386*59036814SCostin Stroie } 387*59036814SCostin Stroie } 388*59036814SCostin Stroie 389*59036814SCostin Stroie 390*59036814SCostin Stroie /** 391*59036814SCostin Stroie * Handler to load page template. 392*59036814SCostin Stroie * 393*59036814SCostin Stroie * @param Doku_Event $event event object by reference 394*59036814SCostin Stroie * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 395*59036814SCostin Stroie * handler was registered] 396*59036814SCostin Stroie * @return void 397*59036814SCostin Stroie */ 398*59036814SCostin Stroie public function handleTemplate(Doku_Event &$event, $param) { 399*59036814SCostin Stroie if (strlen($_REQUEST['copyfrom']) > 0) { 400*59036814SCostin Stroie $template_id = $_REQUEST['copyfrom']; 401*59036814SCostin Stroie if (auth_quickaclcheck($template_id) >= AUTH_READ) { 402*59036814SCostin Stroie $tpl = io_readFile(wikiFN($template_id)); 403*59036814SCostin Stroie if ($this->getConf('replace_id')) { 404*59036814SCostin Stroie $id = $event->data['id']; 405*59036814SCostin Stroie $tpl = str_replace($template_id, $id, $tpl); 406*59036814SCostin Stroie } 407*59036814SCostin Stroie // Add LLM_TEMPLATE metadata if the original page ID contains 'template' 408*59036814SCostin Stroie if (strpos($template_id, 'template') !== false) { 409*59036814SCostin Stroie $tpl = '~~LLM_TEMPLATE:' . $template_id . '~~' . "\n" . $tpl; 410*59036814SCostin Stroie } 411*59036814SCostin Stroie $event->data['tpl'] = $tpl; 412*59036814SCostin Stroie $event->preventDefault(); 413*59036814SCostin Stroie } 414*59036814SCostin Stroie } 415*59036814SCostin Stroie } 416*59036814SCostin Stroie 417*59036814SCostin Stroie 418*59036814SCostin Stroie 419*59036814SCostin Stroie /** 420*59036814SCostin Stroie * Add 'Copy page' button to page tools, SVG based 421*59036814SCostin Stroie * 422*59036814SCostin Stroie * @param Doku_Event $event 423*59036814SCostin Stroie */ 424*59036814SCostin Stroie public function addCopyPageButton(Doku_Event $event) 425*59036814SCostin Stroie { 426*59036814SCostin Stroie global $INFO; 427*59036814SCostin Stroie if ($event->data['view'] != 'page' || !$this->getConf('show_copy_button')) { 428*59036814SCostin Stroie return; 429*59036814SCostin Stroie } 430*59036814SCostin Stroie if (! $INFO['exists']) { 431*59036814SCostin Stroie return; 432*59036814SCostin Stroie } 433*59036814SCostin Stroie array_splice($event->data['items'], -1, 0, [new \dokuwiki\plugin\dokullm\MenuItem()]); 434*59036814SCostin Stroie } 435*59036814SCostin Stroie} 436