Rename project to luxtools, remove client tool
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools;
|
namespace dokuwiki\plugin\luxtools;
|
||||||
|
|
||||||
class Crawler
|
class Crawler
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools;
|
namespace dokuwiki\plugin\luxtools;
|
||||||
|
|
||||||
class Output
|
class Output
|
||||||
{
|
{
|
||||||
@@ -311,7 +311,7 @@ class Output
|
|||||||
|
|
||||||
protected function getLang($key)
|
protected function getLang($key)
|
||||||
{
|
{
|
||||||
$syntax = plugin_load('syntax', 'filetools');
|
$syntax = plugin_load('syntax', 'luxtools');
|
||||||
return $syntax->getLang($key);
|
return $syntax->getLang($key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
Path.php
4
Path.php
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools;
|
namespace dokuwiki\plugin\luxtools;
|
||||||
|
|
||||||
class Path
|
class Path
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@ class Path
|
|||||||
$lastRoot = $line;
|
$lastRoot = $line;
|
||||||
$paths[$line] = [
|
$paths[$line] = [
|
||||||
'root' => $line,
|
'root' => $line,
|
||||||
'web' => DOKU_BASE . 'lib/plugins/filetools/file.php?root=' . rawurlencode($line) . '&file=',
|
'web' => DOKU_BASE . 'lib/plugins/luxtools/file.php?root=' . rawurlencode($line) . '&file=',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
README
6
README
@@ -1,12 +1,12 @@
|
|||||||
File Tools plugin for DokuWiki
|
LuxTools plugin for DokuWiki
|
||||||
|
|
||||||
Lists files matching a given glob pattern.
|
Lists files matching a given glob pattern.
|
||||||
|
|
||||||
All documentation for this plugin can be found at
|
All documentation for this plugin can be found at
|
||||||
https://www.dokuwiki.org/plugin:filetools
|
https://www.dokuwiki.org/plugin:luxtools
|
||||||
|
|
||||||
If you install this plugin manually, make sure it is installed in
|
If you install this plugin manually, make sure it is installed in
|
||||||
lib/plugins/filetools/ - if the folder is called different it
|
lib/plugins/luxtools/ - if the folder is called different it
|
||||||
will not work!
|
will not work!
|
||||||
|
|
||||||
Syntax:
|
Syntax:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools\test;
|
namespace dokuwiki\plugin\luxtools\test;
|
||||||
|
|
||||||
use DokuWikiTest;
|
use DokuWikiTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General tests for the filetools plugin
|
* General tests for the luxtools plugin
|
||||||
*
|
*
|
||||||
* @group plugin_filetools
|
* @group plugin_luxtools
|
||||||
* @group plugins
|
* @group plugins
|
||||||
*/
|
*/
|
||||||
class GeneralTest extends DokuWikiTest
|
class GeneralTest extends DokuWikiTest
|
||||||
@@ -31,7 +31,7 @@ class GeneralTest extends DokuWikiTest
|
|||||||
$this->assertArrayHasKey('desc', $info);
|
$this->assertArrayHasKey('desc', $info);
|
||||||
$this->assertArrayHasKey('url', $info);
|
$this->assertArrayHasKey('url', $info);
|
||||||
|
|
||||||
$this->assertEquals('filetools', $info['base']);
|
$this->assertEquals('luxtools', $info['base']);
|
||||||
$this->assertRegExp('/^https?:\/\//', $info['url']);
|
$this->assertRegExp('/^https?:\/\//', $info['url']);
|
||||||
$this->assertTrue(mail_isvalid($info['email']));
|
$this->assertTrue(mail_isvalid($info['email']));
|
||||||
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
|
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
|
||||||
@@ -61,7 +61,7 @@ class GeneralTest extends DokuWikiTest
|
|||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
gettype($conf),
|
gettype($conf),
|
||||||
gettype($meta),
|
gettype($meta),
|
||||||
'Both ' . DOKU_PLUGIN . 'filetools/conf/default.php and ' . DOKU_PLUGIN . 'filetools/conf/metadata.php have to exist and contain the same keys.'
|
'Both ' . DOKU_PLUGIN . 'luxtools/conf/default.php and ' . DOKU_PLUGIN . 'luxtools/conf/metadata.php have to exist and contain the same keys.'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($conf !== null && $meta !== null) {
|
if ($conf !== null && $meta !== null) {
|
||||||
@@ -69,7 +69,7 @@ class GeneralTest extends DokuWikiTest
|
|||||||
$this->assertArrayHasKey(
|
$this->assertArrayHasKey(
|
||||||
$key,
|
$key,
|
||||||
$meta,
|
$meta,
|
||||||
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'filetools/conf/metadata.php'
|
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'luxtools/conf/metadata.php'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class GeneralTest extends DokuWikiTest
|
|||||||
$this->assertArrayHasKey(
|
$this->assertArrayHasKey(
|
||||||
$key,
|
$key,
|
||||||
$conf,
|
$conf,
|
||||||
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'filetools/conf/default.php'
|
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'luxtools/conf/default.php'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools\test;
|
namespace dokuwiki\plugin\luxtools\test;
|
||||||
|
|
||||||
use dokuwiki\plugin\filetools\Path;
|
use dokuwiki\plugin\luxtools\Path;
|
||||||
use DokuWikiTest;
|
use DokuWikiTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path related tests for the filetools plugin
|
* Path related tests for the luxtools plugin
|
||||||
*
|
*
|
||||||
* @group plugin_filetools
|
* @group plugin_luxtools
|
||||||
* @group plugins
|
* @group plugins
|
||||||
*/
|
*/
|
||||||
class PathTest extends DokuWikiTest
|
class PathTest extends DokuWikiTest
|
||||||
@@ -40,15 +40,15 @@ EOT
|
|||||||
$expect = [
|
$expect = [
|
||||||
'C:/xampp/htdocs/wiki/' => [
|
'C:/xampp/htdocs/wiki/' => [
|
||||||
'root' => 'C:/xampp/htdocs/wiki/',
|
'root' => 'C:/xampp/htdocs/wiki/',
|
||||||
'web' => '/lib/plugins/filetools/file.php?root=C%3A%2Fxampp%2Fhtdocs%2Fwiki%2F&file=',
|
'web' => '/lib/plugins/luxtools/file.php?root=C%3A%2Fxampp%2Fhtdocs%2Fwiki%2F&file=',
|
||||||
],
|
],
|
||||||
'\\\\server/share/path/' => [
|
'\\\\server/share/path/' => [
|
||||||
'root' => '\\\\server/share/path/',
|
'root' => '\\\\server/share/path/',
|
||||||
'web' => '/lib/plugins/filetools/file.php?root=%5C%5Cserver%2Fshare%2Fpath%2F&file=',
|
'web' => '/lib/plugins/luxtools/file.php?root=%5C%5Cserver%2Fshare%2Fpath%2F&file=',
|
||||||
],
|
],
|
||||||
'/linux/file/path/' => [
|
'/linux/file/path/' => [
|
||||||
'root' => '/linux/file/path/',
|
'root' => '/linux/file/path/',
|
||||||
'web' => '/lib/plugins/filetools/file.php?root=%2Flinux%2Ffile%2Fpath%2F&file=',
|
'web' => '/lib/plugins/luxtools/file.php?root=%2Flinux%2Ffile%2Fpath%2F&file=',
|
||||||
],
|
],
|
||||||
'/linux/another/path/' => [
|
'/linux/another/path/' => [
|
||||||
'root' => '/linux/another/path/',
|
'root' => '/linux/another/path/',
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace dokuwiki\plugin\filetools\test;
|
namespace dokuwiki\plugin\luxtools\test;
|
||||||
|
|
||||||
use DokuWikiTest;
|
use DokuWikiTest;
|
||||||
use DOMWrap\Document;
|
use DOMWrap\Document;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the filetools plugin.
|
* Tests for the luxtools plugin.
|
||||||
*
|
*
|
||||||
* These test assume that the directory filetools has the following content:
|
* These test assume that the directory luxtools has the following content:
|
||||||
* - exampledir (directory)
|
* - exampledir (directory)
|
||||||
* - example2.txt (text file)
|
* - example2.txt (text file)
|
||||||
* - example.txt (text file)
|
* - example.txt (text file)
|
||||||
* - exampleimage.png (image file)
|
* - exampleimage.png (image file)
|
||||||
*
|
*
|
||||||
* @group plugin_filetools
|
* @group plugin_luxtools
|
||||||
* @group plugins
|
* @group plugins
|
||||||
*/
|
*/
|
||||||
class plugin_filetools_test extends DokuWikiTest
|
class plugin_luxtools_test extends DokuWikiTest
|
||||||
{
|
{
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
global $conf;
|
global $conf;
|
||||||
|
|
||||||
$this->pluginsEnabled[] = 'filetools';
|
$this->pluginsEnabled[] = 'luxtools';
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
// Setup config so that access to the TMP directory will be allowed
|
// Setup config so that access to the TMP directory will be allowed
|
||||||
$conf ['plugin']['filetools']['paths'] = TMP_DIR . '/filelistdata/' . "\n" . 'W> http://localhost/';
|
$conf ['plugin']['luxtools']['paths'] = TMP_DIR . '/filelistdata/' . "\n" . 'W> http://localhost/';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
file.php
4
file.php
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
|
// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
|
||||||
|
|
||||||
use dokuwiki\plugin\filetools\Path;
|
use dokuwiki\plugin\luxtools\Path;
|
||||||
|
|
||||||
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../../');
|
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../../');
|
||||||
if (!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
|
if (!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
|
||||||
@@ -11,7 +11,7 @@ require_once(DOKU_INC . 'inc/init.php');
|
|||||||
|
|
||||||
global $INPUT;
|
global $INPUT;
|
||||||
|
|
||||||
$syntax = plugin_load('syntax', 'filetools');
|
$syntax = plugin_load('syntax', 'luxtools');
|
||||||
if (!$syntax) die('plugin disabled?');
|
if (!$syntax) die('plugin disabled?');
|
||||||
|
|
||||||
$pathUtil = new Path($syntax->getConf('paths'));
|
$pathUtil = new Path($syntax->getConf('paths'));
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module filetools-local-opener
|
|
||||||
|
|
||||||
go 1.22
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type allowList []string
|
|
||||||
|
|
||||||
func (a *allowList) String() string { return strings.Join(*a, ",") }
|
|
||||||
func (a *allowList) Set(value string) error {
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
if value == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*a = append(*a, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type openRequest struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type openResponse struct {
|
|
||||||
OK bool `json:"ok"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
listen := flag.String("listen", "127.0.0.1:8765", "listen address (host:port), should be loopback")
|
|
||||||
token := flag.String("token", "", "shared secret token; if empty, requests are allowed without authentication")
|
|
||||||
var allowed allowList
|
|
||||||
flag.Var(&allowed, "allow", "allowed path prefix (repeatable); if none, any path is allowed")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if !isLoopbackListenAddr(*listen) {
|
|
||||||
log.Fatalf("refusing to listen on non-loopback address: %s", *listen)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(*token) == "" {
|
|
||||||
generated, err := generateToken()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to generate token: %v", err)
|
|
||||||
}
|
|
||||||
*token = generated
|
|
||||||
log.Printf("generated token (set this in the plugin config): %s", *token)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
|
|
||||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
withCORS(w, r)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "time": time.Now().Format(time.RFC3339)})
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/open", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
withCORS(w, r)
|
|
||||||
if r.Method == http.MethodOptions {
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req openRequest
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
req.Path = r.URL.Query().Get("path")
|
|
||||||
case http.MethodPost:
|
|
||||||
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32*1024))
|
|
||||||
dec.DisallowUnknownFields()
|
|
||||||
if err := dec.Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, openResponse{OK: false, Message: "invalid json"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, openResponse{OK: false, Message: "GET or POST required"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkToken(r, *token) {
|
|
||||||
// Allow token to be supplied via query string for GET fallback.
|
|
||||||
qt := strings.TrimSpace(r.URL.Query().Get("token"))
|
|
||||||
if qt == "" || !subtleStringEqual(qt, strings.TrimSpace(*token)) {
|
|
||||||
writeJSON(w, http.StatusUnauthorized, openResponse{OK: false, Message: "unauthorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target, err := normalizePath(req.Path)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, openResponse{OK: false, Message: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allowed) > 0 && !isAllowed(target, allowed) {
|
|
||||||
writeJSON(w, http.StatusForbidden, openResponse{OK: false, Message: "path not allowed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := openFolder(target); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, openResponse{OK: false, Message: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
// For GET callers (image-ping), a 204 avoids console noise from non-image responses.
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, openResponse{OK: true, Message: "opened"})
|
|
||||||
})
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Addr: *listen,
|
|
||||||
Handler: mux,
|
|
||||||
ReadHeaderTimeout: 5 * time.Second,
|
|
||||||
ReadTimeout: 10 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
IdleTimeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("listening on http://%s", *listen)
|
|
||||||
log.Printf("os=%s arch=%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLoopbackListenAddr(addr string) bool {
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ip := net.ParseIP(host)
|
|
||||||
if ip == nil {
|
|
||||||
return host == "localhost"
|
|
||||||
}
|
|
||||||
return ip.IsLoopback()
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateToken() (string, error) {
|
|
||||||
b := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func withCORS(w http.ResponseWriter, r *http.Request) {
|
|
||||||
origin := r.Header.Get("Origin")
|
|
||||||
if origin != "" {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
||||||
w.Header().Set("Vary", "Origin")
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
}
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Filetools-Token")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkToken(r *http.Request, required string) bool {
|
|
||||||
required = strings.TrimSpace(required)
|
|
||||||
if required == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
got := r.Header.Get("X-Filetools-Token")
|
|
||||||
got = strings.TrimSpace(got)
|
|
||||||
return got != "" && subtleStringEqual(got, required)
|
|
||||||
}
|
|
||||||
|
|
||||||
func subtleStringEqual(a, b string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var v byte
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
v |= a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
return v == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizePath(input string) (string, error) {
|
|
||||||
p := strings.TrimSpace(input)
|
|
||||||
if p == "" {
|
|
||||||
return "", errors.New("missing path")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept file:// URLs.
|
|
||||||
if strings.HasPrefix(strings.ToLower(p), "file://") {
|
|
||||||
p = strings.TrimPrefix(p, "file://")
|
|
||||||
// file:///C:/... becomes /C:/... (strip one leading slash)
|
|
||||||
p = strings.TrimPrefix(p, "/")
|
|
||||||
p = strings.TrimPrefix(p, "/")
|
|
||||||
p = strings.TrimPrefix(p, "/")
|
|
||||||
p = strings.ReplaceAll(p, "/", string(os.PathSeparator))
|
|
||||||
}
|
|
||||||
|
|
||||||
p = filepath.Clean(p)
|
|
||||||
if !filepath.IsAbs(p) {
|
|
||||||
return "", errors.New("path must be absolute")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure path exists.
|
|
||||||
st, err := os.Stat(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("path not found")
|
|
||||||
}
|
|
||||||
if !st.IsDir() {
|
|
||||||
// If a file is provided, open its containing folder.
|
|
||||||
p = filepath.Dir(p)
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAllowed(path string, allowed []string) bool {
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
for _, a := range allowed {
|
|
||||||
a = filepath.Clean(a)
|
|
||||||
if a == "." || a == string(os.PathSeparator) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Case-insensitive on Windows.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if strings.HasPrefix(strings.ToLower(path), strings.ToLower(a)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(path, a) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFolder(path string) error {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
// explorer requires backslashes.
|
|
||||||
p := strings.ReplaceAll(path, "/", "\\")
|
|
||||||
cmd := exec.Command("explorer.exe", p)
|
|
||||||
return cmd.Start()
|
|
||||||
case "linux":
|
|
||||||
cmd := exec.Command("xdg-open", path)
|
|
||||||
return cmd.Start()
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeJSON(w http.ResponseWriter, status int, resp openResponse) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
base filetools
|
base luxtools
|
||||||
author Gina Häußge, Dokufreaks, luxick
|
author Gina Häußge, Dokufreaks, luxick
|
||||||
email dokuwiki@luxick.de
|
email dokuwiki@luxick.de
|
||||||
date 2026-01-05
|
date 2026-01-05
|
||||||
name File Tools
|
name LuxTools
|
||||||
desc Lists files matching a given glob pattern.
|
desc Lists files matching a given glob pattern.
|
||||||
url https://www.dokuwiki.org/plugin:filetools
|
url https://www.dokuwiki.org/plugin:luxtools
|
||||||
|
|||||||
23
syntax.php
23
syntax.php
@@ -3,18 +3,29 @@
|
|||||||
require_once(__DIR__ . '/syntax/files.php');
|
require_once(__DIR__ . '/syntax/files.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Tools plugin compatibility shim.
|
* LuxTools plugin bootstrap.
|
||||||
*
|
|
||||||
* Keep this class so existing code that does `plugin_load('syntax', 'filetools')`
|
|
||||||
* continues to work (config/lang access).
|
|
||||||
*
|
*
|
||||||
* The actual {{files>...}} syntax implementation lives in syntax/files.php.
|
* The actual {{files>...}} syntax implementation lives in syntax/files.php.
|
||||||
*/
|
*/
|
||||||
class syntax_plugin_filetools extends syntax_plugin_filetools_files
|
class syntax_plugin_luxtools extends syntax_plugin_luxtools_files
|
||||||
{
|
{
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function connectTo($mode)
|
public function connectTo($mode)
|
||||||
{
|
{
|
||||||
// Intentionally empty: syntax is registered by syntax_plugin_filetools_files.
|
// Intentionally empty: syntax is registered by syntax_plugin_luxtools_files.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility alias for older codebases that referenced the legacy class name.
|
||||||
|
*
|
||||||
|
* Note: plugin id/base is now `luxtools`.
|
||||||
|
*/
|
||||||
|
class syntax_plugin_filetools extends syntax_plugin_luxtools_files
|
||||||
|
{
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function connectTo($mode)
|
||||||
|
{
|
||||||
|
// Intentionally empty: syntax is registered by syntax_plugin_luxtools_files.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use dokuwiki\Extension\SyntaxPlugin;
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
use dokuwiki\plugin\filetools\Crawler;
|
use dokuwiki\plugin\luxtools\Crawler;
|
||||||
use dokuwiki\plugin\filetools\Output;
|
use dokuwiki\plugin\luxtools\Output;
|
||||||
use dokuwiki\plugin\filetools\Path;
|
use dokuwiki\plugin\luxtools\Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Tools Plugin: Files syntax.
|
* LuxTools Plugin: Files syntax.
|
||||||
*
|
*
|
||||||
* Lists files matching a given glob pattern.
|
* Lists files matching a given glob pattern.
|
||||||
*/
|
*/
|
||||||
class syntax_plugin_filetools_files extends SyntaxPlugin
|
class syntax_plugin_luxtools_files extends SyntaxPlugin
|
||||||
{
|
{
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function getType()
|
public function getType()
|
||||||
@@ -33,7 +33,7 @@ class syntax_plugin_filetools_files extends SyntaxPlugin
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function connectTo($mode)
|
public function connectTo($mode)
|
||||||
{
|
{
|
||||||
$this->Lexer->addSpecialPattern('\{\{files>.+?\}\}', $mode, 'plugin_filetools_files');
|
$this->Lexer->addSpecialPattern('\{\{files>.+?\}\}', $mode, 'plugin_luxtools_files');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use dokuwiki\Extension\SyntaxPlugin;
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
use dokuwiki\plugin\filetools\Crawler;
|
use dokuwiki\plugin\luxtools\Crawler;
|
||||||
use dokuwiki\plugin\filetools\Output;
|
use dokuwiki\plugin\luxtools\Output;
|
||||||
use dokuwiki\plugin\filetools\Path;
|
use dokuwiki\plugin\luxtools\Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Tools Plugin: Image gallery syntax.
|
* LuxTools Plugin: Image gallery syntax.
|
||||||
*
|
*
|
||||||
* Renders a thumbnail gallery of images matching a glob pattern.
|
* Renders a thumbnail gallery of images matching a glob pattern.
|
||||||
*/
|
*/
|
||||||
class syntax_plugin_filetools_images extends SyntaxPlugin
|
class syntax_plugin_luxtools_images extends SyntaxPlugin
|
||||||
{
|
{
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function getType()
|
public function getType()
|
||||||
@@ -33,7 +33,7 @@ class syntax_plugin_filetools_images extends SyntaxPlugin
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function connectTo($mode)
|
public function connectTo($mode)
|
||||||
{
|
{
|
||||||
$this->Lexer->addSpecialPattern('\{\{images>.+?\}\}', $mode, 'plugin_filetools_images');
|
$this->Lexer->addSpecialPattern('\{\{images>.+?\}\}', $mode, 'plugin_luxtools_images');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
use dokuwiki\Extension\SyntaxPlugin;
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Tools Plugin: Open local path syntax.
|
* LuxTools Plugin: Open local path syntax.
|
||||||
*
|
*
|
||||||
* Renders an inline button. Clicking it triggers client-side JS that attempts
|
* Renders an inline button. Clicking it triggers client-side JS that attempts
|
||||||
* to open the configured path in the default file manager (best-effort).
|
* to open the configured path in the default file manager (best-effort).
|
||||||
*/
|
*/
|
||||||
class syntax_plugin_filetools_open extends SyntaxPlugin
|
class syntax_plugin_luxtools_open extends SyntaxPlugin
|
||||||
{
|
{
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function getType()
|
public function getType()
|
||||||
@@ -32,7 +32,7 @@ class syntax_plugin_filetools_open extends SyntaxPlugin
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function connectTo($mode)
|
public function connectTo($mode)
|
||||||
{
|
{
|
||||||
$this->Lexer->addSpecialPattern('\{\{open>.+?\}\}', $mode, 'plugin_filetools_open');
|
$this->Lexer->addSpecialPattern('\{\{open>.+?\}\}', $mode, 'plugin_luxtools_open');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|||||||
Reference in New Issue
Block a user