1# DokuWiki Plugin: localopen 2 3Open local files and folders directly from DokuWiki pages — **without browser popups or protocol handlers**. 4 5This plugin replaces `localexplorer://`-style links with a modern, secure approach using a local HTTP service and background requests (`fetch()`), resulting in a seamless, one-click experience. 6 7--- 8 9## ✨ Features 10 11- Open local files (`.docx`, `.pdf`, etc.) and folders from wiki links 12- No browser confirmation dialogs 13- No blank tabs or navigation away from the wiki 14- Works with: 15 - Local drives (`C:\`, `Z:\`, etc.) 16 - Network shares (`\\server\share`) 17- Uses standard HTTP instead of custom protocol handlers 18- Lightweight and fast 19 20--- 21 22## How It Works 23 241. The plugin renders links like: \[\[localopen>c:\windows|Windows\]\] 25 262. When clicked: 27- A background `fetch()` request is sent to: 28 29 ``` 30 http://127.0.0.1:2222/open?path=...&token=... 31 ``` 32- A small Python service running locally receives the request 33- The service opens the file using Windows (`os.startfile`) 34 35 The browser never navigates away from the page. 36 37--- 38 39## Requirements 40 41- DokuWiki 42- Python 3 (Windows) 43- A local Python service (included below) 44 45--- 46 47## Installation 48 49### 1. Install the Plugin 50 51Copy the plugin into: lib/plugins/localopen/ 52 53--- 54 55### 2. Set Your Token 56 57Choose a secret token (any random string) 58 59 60Update it in: 61 62- the plugin PHP file 63- the Python service 64 65--- 66 67### 3. Run the Local Python Service 68 69Save this as `localopen_service.py`: 70 71```python 72from http.server import BaseHTTPRequestHandler, HTTPServer 73from urllib.parse import urlparse, parse_qs, unquote 74import os 75 76HOST = "127.0.0.1" 77PORT = 2222 78TOKEN = "ChangeThisToYourToken" 79 80ALLOWED_PREFIXES = [ 81 r"C:\\", 82 r"D:\\", 83 r"Z:\\", 84 r"\\10.2.1.74\\", 85] 86 87def normalize_path(path): 88 path = unquote(path) 89 return path.strip().strip('"').strip("'") 90 91def is_allowed(path): 92 p = path.lower() 93 return any(p.startswith(prefix.lower()) for prefix in ALLOWED_PREFIXES) 94 95class Handler(BaseHTTPRequestHandler): 96 def do_GET(self): 97 parsed = urlparse(self.path) 98 99 if parsed.path != "/open": 100 self.send_error(404) 101 return 102 103 qs = parse_qs(parsed.query) 104 token = qs.get("token", [""])[0] 105 path = normalize_path(qs.get("path", [""])[0]) 106 107 if token != TOKEN: 108 self.send_error(403) 109 return 110 111 if not is_allowed(path): 112 self.send_error(403) 113 return 114 115 if not os.path.exists(path): 116 self.send_error(404) 117 return 118 119 os.startfile(path) 120 121 self.send_response(200) 122 self.end_headers() 123 124 def log_message(self, *args): 125 pass 126 127HTTPServer((HOST, PORT), Handler).serve_forever() 128 129