VN Suche - Mobirise Block ( Komplett System)

Anwendungen für Webseiten. Künstliche Intelligenz verwenden.
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

juhu :D

Man war das ein Ritt. Jetzt ist die vn suche gefixt. Schalter aus - kein Oredner mehr im assets. Schalter an dann erst ordner im assetes.
Bitte erstmal nur vn search runter laden. die anderen mach ich heute abend fertig.
Ist also morgen früh alles gefixt!
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Folgende Anwendungen sind nun gefixt:

vn newsletter
vn upload
vn suche
vn form renderer

Diese Anwendungen erstellen nach wie vor eigene Ornder im assets Ordner.
Wenn man jedoch diese Anwendungen aus schaltet werden keine Ordner mehr veröffentlicht.
Vorhandene muss man von Hand auf dem Server löschen.
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Tommy Herrmann hat geschrieben: Mo 3. Aug 2026, 11:33
> Ich habe auch festgestellt, dass Dateien in mittels .htaccess geschützten Verzeichnissen gefunden und gelistet werden, das sollte auch unterbunden werden. Verzeichnisse werden ja genau deswegen mittels .htaccess geschützt.
Das habe ich gefixt htaccess wird übersprungen ;)

Wir können uns jetzt um die Suche kümmern Tommy. Das Problem mit den Ordnern ist ja nun gelöst.

Probier mal die neue Suche. Bei mir gehen ja alle Seiten und PDF werden gefunden und auch im richtigen Pfad geöffnet.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Moin Volker,

was ist das denn für eine Krücke :lachtot:

Du solltest auch den Namen "Witsec" auf gar keinen Fall in irgendeienm Deiner Scripts verwenden, das kann richtig Ärger geben.

"xyz123switch": false

Code: Alles auswählen

// Wie bei Witsec: explizit "false" setzen beim Abwählen, da eine
// abgewählte Checkbox sonst keinen Wert ueberträgt und der alte
// Wert einfach stehen bleiben wuerde.
mbrApp.$body.on("change", "#xyz123switch-toggle", function () {
    if (!$(this).prop("checked")) {
        a.projectSettings["xyz123switch"] = false;
    }
});

Ich habe jetzt diese Anwendungen neu installiert:

VN Mailer
VN Form Renderer
VN Suche


... - sind die schon alle mit dem "Schalter" repariert? Ich habe nur "VN Suche" getestet, diese funktioniert nun auch wenn der Schalter mal auf "ein" stand. Man kann diesen nun dauerhaft ausschalten.


VN Suche

Also manchmal frage ich mich warum Deine KI sich so neu anstellt - oder sind das dann doch Deine Fehler?

Auch hier wieder - ich habe das Script "search.php" meiner KI ChatGPT zur Prüfung vorgelegt und sie hat den Fehler mit den Verzeichnissen sofort erkannt - übrigens auch bei normalen Dateien verbessert - und auch gleich das mit der .htaccess von der Suche ausgeschlossen.

Dauer der Fehleranalyse: 2 Sekunden, Dauer der neuen Generierung 54 Sekunden :confused:

Jetzt funktioniert die Suche bezüglich der "pdf" Dateien einwandfrei und es werden keine Dateien mehr in geschützten Verzeichnissen gelistet.

Bitte bei mir testen:

↗️ https://www.mobirise-tutorials.com/VN-B ... .php#Suche


Warum das vorher bei Dir funktioniert hat habe ich nicht gefragt, denn die Suche war eindeutig falsch und hat immer im Verzeichnis der Anwendung begonnen. Das kann also bei Dir eigentlich nur dann funktioniert haben, wenn Du die Anwendung im Root hattest und nicht ein Verzeichnis tiefer (wie ich und wie Du behauptet hast).

Die Suche muss immer funktionieren, ganz egal in welchem Verzeichnis die Anwendung liegt.

Unten das reparierte Script "search.php". Bitte prüfen und nach Einbau nochmals Bescheid geben, dann möchte ich die .mbrext-Datei "VN Suche" natürlich auch nochmals korrekt neu importieren.

Code: Alles auswählen

<?php

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');


/*
=========================================================
JSON-ANTWORT AUSGEBEN
=========================================================
*/

function vnSearchRespond(
    bool $success,
    string $message,
    array $extra = []
): void {
    http_response_code($success ? 200 : 400);

    echo json_encode(
        array_merge(
            [
                'success' => $success,
                'message' => $message
            ],
            $extra
        ),
        JSON_UNESCAPED_UNICODE
    );

    exit;
}


/*
=========================================================
DATENBANK UND HILFSFUNKTIONEN LADEN
=========================================================
*/

require __DIR__ . '/db.php';


/*
=========================================================
FALLBACK, FALLS MBSTRING NICHT VERFÜGBAR IST
=========================================================
*/

if (!function_exists('mb_strlen')) {
    function mb_strlen(
        string $str,
        string $encoding = 'UTF-8'
    ): int {
        return strlen($str);
    }
}


/*
=========================================================
URL IN EINEN LOKALEN DATEIPFAD UMWANDELN
=========================================================

Die Suchdatenbank enthält URLs wie beispielsweise:

    /Mobirise-Tutorials/Download-Archiv/archiv/Demo.pdf

Für die Prüfung einer eventuell vorhandenen .htaccess
benötigen wir den tatsächlichen Dateipfad auf dem Server.
*/

function vnSearchUrlToLocalPath(string $url): ?string
{
    $url = trim($url);

    if ($url === '') {
        return null;
    }

    /*
    Anker und reine Abfrageparameter sind keine Dateipfade.
    */

    if (
        str_starts_with($url, '#')
        || str_starts_with($url, '?')
    ) {
        return null;
    }

    /*
    Bei absoluten URLs nur URLs der eigenen Domain prüfen.
    Externe URLs werden nicht als lokale Dateien behandelt.
    */

    $urlParts = parse_url($url);

    if ($urlParts === false) {
        return null;
    }

    if (isset($urlParts['host'])) {
        $urlHost = strtolower((string)$urlParts['host']);

        $currentHost = strtolower(
            preg_replace(
                '/:\d+$/',
                '',
                (string)($_SERVER['HTTP_HOST'] ?? '')
            )
        );

        if (
            $currentHost === ''
            || $urlHost !== $currentHost
        ) {
            return null;
        }
    }

    $urlPath = (string)($urlParts['path'] ?? '');

    if ($urlPath === '') {
        return null;
    }

    /*
    URL-Codierungen wie %20 wieder in echte Zeichen
    umwandeln.
    */

    $urlPath = rawurldecode($urlPath);

    /*
    Query-String und Fragment sind durch parse_url bereits
    entfernt worden.
    */

    $urlPath = str_replace('\\', '/', $urlPath);

    /*
    Der Document-Root ist das oberste öffentlich erreichbare
    Verzeichnis der Domain.
    */

    $documentRoot = realpath(
        (string)($_SERVER['DOCUMENT_ROOT'] ?? '')
    );

    if (
        $documentRoot === false
        || !is_dir($documentRoot)
    ) {
        return null;
    }

    /*
    Führenden Schrägstrich entfernen, damit der URL-Pfad
    korrekt an den Document-Root angehängt werden kann.
    */

    $relativePath = ltrim($urlPath, '/');

    if ($relativePath === '') {
        return $documentRoot;
    }

    /*
    "../"-Bestandteile verhindern, damit niemals außerhalb
    des Document-Roots gesucht wird.
    */

    $segments = explode('/', $relativePath);
    $safeSegments = [];

    foreach ($segments as $segment) {
        $segment = trim($segment);

        if (
            $segment === ''
            || $segment === '.'
        ) {
            continue;
        }

        if ($segment === '..') {
            return null;
        }

        $safeSegments[] = $segment;
    }

    if ($safeSegments === []) {
        return $documentRoot;
    }

    $localPath =
        $documentRoot
        . DIRECTORY_SEPARATOR
        . implode(DIRECTORY_SEPARATOR, $safeSegments);

    return $localPath;
}


/*
=========================================================
PRÜFEN, OB EINE .HTACCESS ZUGRIFFSSCHUTZ ENTHÄLT
=========================================================

Nicht jede .htaccess sperrt ein Verzeichnis.

Viele .htaccess-Dateien enthalten lediglich:

- Weiterleitungen
- PHP-Einstellungen
- Cache-Regeln
- Fehlerseiten
- URL-Rewrite-Regeln

Deshalb wird nicht schon das bloße Vorhandensein einer
.htaccess als Sperre gewertet. Es werden typische
Zugriffsschutz-Anweisungen gesucht.
*/

function vnSearchHtaccessIsProtected(string $htaccessFile): bool
{
    if (
        !is_file($htaccessFile)
        || !is_readable($htaccessFile)
    ) {
        return false;
    }

    /*
    Schutz gegen ungewöhnlich große Dateien.
    Für eine .htaccess sind 256 KB mehr als ausreichend.
    */

    $fileSize = filesize($htaccessFile);

    if (
        $fileSize !== false
        && $fileSize > 262144
    ) {
        return false;
    }

    $content = file_get_contents($htaccessFile);

    if ($content === false || $content === '') {
        return false;
    }

    /*
    Vollständige Kommentarzeilen entfernen.
    */

    $content = preg_replace(
        '/^[ \t]*#.*$/m',
        '',
        $content
    );

    if (!is_string($content) || trim($content) === '') {
        return false;
    }

    /*
    Typischer Passwortschutz mit Basic Authentication:

        AuthType Basic
        AuthName "Geschützter Bereich"
        AuthUserFile /pfad/.htpasswd
        Require valid-user
    */

    $protectionPatterns = [

        /*
        Apache-Passwortschutz.
        */

        '/^[ \t]*AuthType[ \t]+/mi',
        '/^[ \t]*AuthUserFile[ \t]+/mi',
        '/^[ \t]*AuthGroupFile[ \t]+/mi',

        /*
        Zugriff nur für angemeldete Benutzer oder bestimmte
        Benutzer beziehungsweise Gruppen.
        */

        '/^[ \t]*Require[ \t]+valid-user\b/mi',
        '/^[ \t]*Require[ \t]+user[ \t]+/mi',
        '/^[ \t]*Require[ \t]+group[ \t]+/mi',

        /*
        Zugriff vollständig verbieten.
        */

        '/^[ \t]*Require[ \t]+all[ \t]+denied\b/mi',
        '/^[ \t]*Deny[ \t]+from[ \t]+all\b/mi',

        /*
        Ältere Apache-2.2-Konfigurationen können den Zugriff
        standardmäßig verweigern und nur bestimmte Besucher
        zulassen.
        */

        '/^[ \t]*Order[ \t]+deny[ \t]*,[ \t]*allow\b/mi',
        '/^[ \t]*Order[ \t]+allow[ \t]*,[ \t]*deny\b/mi'
    ];

    foreach ($protectionPatterns as $pattern) {
        if (preg_match($pattern, $content) === 1) {
            return true;
        }
    }

    return false;
}


/*
=========================================================
PRÜFEN, OB EIN SUCHTREFFER GESCHÜTZT IST
=========================================================

Es wird im Verzeichnis der gefundenen Datei begonnen und
anschließend Verzeichnis für Verzeichnis nach oben geprüft.

Beispiel:

    /Mobirise-Tutorials/Download-Archiv/archiv/Demo.pdf

Geprüft werden:

    /Mobirise-Tutorials/Download-Archiv/archiv/.htaccess
    /Mobirise-Tutorials/Download-Archiv/.htaccess
    /Mobirise-Tutorials/.htaccess

Die .htaccess direkt im Root der Domain wird absichtlich
nicht ausgewertet, weil dort oft globale Rewrite- oder
PHP-Regeln stehen.
*/

function vnSearchResultIsProtected(string $url): bool
{
    static $directoryCache = [];

    $localPath = vnSearchUrlToLocalPath($url);

    if ($localPath === null) {
        return false;
    }

    $documentRoot = realpath(
        (string)($_SERVER['DOCUMENT_ROOT'] ?? '')
    );

    if ($documentRoot === false) {
        return false;
    }

    $documentRoot = rtrim(
        $documentRoot,
        DIRECTORY_SEPARATOR
    );

    /*
    Bei einer Datei beginnen wir in ihrem Elternverzeichnis.
    Existiert der Treffer nicht mehr, verwenden wir ebenfalls
    dirname(), da die URL trotzdem einen Dateinamen enthalten
    kann.
    */

    if (is_dir($localPath)) {
        $currentDirectory = $localPath;
    } else {
        $currentDirectory = dirname($localPath);
    }

    /*
    realpath funktioniert nur bei tatsächlich vorhandenen
    Verzeichnissen. Falls der Pfad nicht mehr existiert,
    versuchen wir schrittweise ein vorhandenes
    Elternverzeichnis zu finden.
    */

    while (
        !is_dir($currentDirectory)
        && $currentDirectory !== dirname($currentDirectory)
    ) {
        $currentDirectory = dirname($currentDirectory);
    }

    $resolvedDirectory = realpath($currentDirectory);

    if ($resolvedDirectory === false) {
        return false;
    }

    $currentDirectory = rtrim(
        $resolvedDirectory,
        DIRECTORY_SEPARATOR
    );

    /*
    Sicherheitsprüfung: Das Verzeichnis muss innerhalb des
    Document-Roots liegen.
    */

    if (
        $currentDirectory !== $documentRoot
        && !str_starts_with(
            $currentDirectory . DIRECTORY_SEPARATOR,
            $documentRoot . DIRECTORY_SEPARATOR
        )
    ) {
        return false;
    }

    while (
        $currentDirectory !== ''
        && $currentDirectory !== $documentRoot
    ) {
        if (array_key_exists($currentDirectory, $directoryCache)) {
            if ($directoryCache[$currentDirectory] === true) {
                return true;
            }
        } else {
            $htaccessFile =
                $currentDirectory
                . DIRECTORY_SEPARATOR
                . '.htaccess';

            $isProtected = vnSearchHtaccessIsProtected(
                $htaccessFile
            );

            $directoryCache[$currentDirectory] = $isProtected;

            if ($isProtected) {
                return true;
            }
        }

        $parentDirectory = dirname($currentDirectory);

        if (
            $parentDirectory === $currentDirectory
            || $parentDirectory === ''
        ) {
            break;
        }

        $currentDirectory = rtrim(
            $parentDirectory,
            DIRECTORY_SEPARATOR
        );
    }

    return false;
}


/*
=========================================================
SUCHPARAMETER EINLESEN
=========================================================
*/

$query = trim((string)($_GET['q'] ?? ''));

if ($query === '' || mb_strlen($query) < 2) {
    vnSearchRespond(
        true,
        '',
        [
            'results' => [],
            'count'   => 0,
            'total'   => 0
        ]
    );
}


/*
Mögliche Werte:

all  = alle Treffer
page = nur HTML-Seiten
pdf  = nur PDF-Dateien
*/

$typeFilter = trim((string)($_GET['type'] ?? 'all'));

if (
    $typeFilter !== 'page'
    && $typeFilter !== 'pdf'
) {
    $typeFilter = 'all';
}


/*
Maximale Anzahl der ausgegebenen Ergebnisse.
Der Wert wird sicherheitshalber auf 1 bis 400 begrenzt.
*/

$limit = min(
    400,
    max(
        1,
        (int)($_GET['limit'] ?? 10)
    )
);


/*
Anzahl der Wörter im Textausschnitt.
Der Wert wird auf 6 bis 64 begrenzt.
*/

$snippetWords = min(
    64,
    max(
        6,
        (int)($_GET['snippet'] ?? 16)
    )
);


/*
=========================================================
FTS-SUCHANFRAGE ERSTELLEN
=========================================================
*/

$ftsQuery = vnSearchBuildFtsQuery($query);

if ($ftsQuery === '') {
    vnSearchRespond(
        true,
        '',
        [
            'results' => [],
            'count'   => 0,
            'total'   => 0
        ]
    );
}


/*
=========================================================
DATENBANKVERBINDUNG HERSTELLEN
=========================================================
*/

$pdo = vnSearchDb();


/*
=========================================================
SUCHERGEBNISSE ABFRAGEN
=========================================================

Hier wird zunächst ohne das vom Frontend gewünschte LIMIT
abgefragt.

Der Grund:

Würden beispielsweise von den ersten zehn Datenbanktreffern
drei Treffer in geschützten Verzeichnissen liegen, könnten
sonst nur sieben Ergebnisse ausgegeben werden, obwohl noch
weitere öffentliche Treffer vorhanden sind.

Erst nach dem Entfernen geschützter Treffer wird das
eigentliche LIMIT angewendet.
*/

$sql = '
    SELECT
        title,
        url,
        type,
        snippet(
            search_index,
            1,
            "<mark>",
            "</mark>",
            "…",
            :snip
        ) AS snippet,
        rank
    FROM search_index
    WHERE search_index MATCH :q
';

$params = [
    'q' => $ftsQuery
];

if ($typeFilter === 'page' || $typeFilter === 'pdf') {
    $sql .= ' AND type = :ty';
    $params['ty'] = $typeFilter;
}

$sql .= ' ORDER BY rank';

$stmt = $pdo->prepare($sql);

foreach ($params as $key => $value) {
    $stmt->bindValue(
        ':' . $key,
        $value
    );
}

$stmt->bindValue(
    ':snip',
    $snippetWords,
    PDO::PARAM_INT
);

$stmt->execute();

$databaseResults = $stmt->fetchAll();


/*
=========================================================
ERSTES SINNVOLLES SUCHWORT ERMITTELN
=========================================================

Bei normalen HTML-Seiten wird zusätzlich ein Textfragment
an die URL angehängt:

    #:~:text=Suchwort

Damit kann der Browser direkt zur Fundstelle springen und
das gefundene Wort hervorheben.

Für PDF-Dateien wird dieses Fragment nicht angehängt.
*/

$words = preg_split(
    '/\s+/',
    trim($query),
    -1,
    PREG_SPLIT_NO_EMPTY
);

$firstWord = '';

if (is_array($words)) {
    foreach ($words as $word) {
        $clean = preg_replace(
            '/[^\p{L}\p{N}]/u',
            '',
            $word
        );

        if (is_string($clean) && $clean !== '') {
            $firstWord = $clean;
            break;
        }
    }
}


/*
=========================================================
SUCHERGEBNISSE AUFBEREITEN
=========================================================
*/

$publicResults = [];

foreach ($databaseResults as $result) {

    $url = trim(
        (string)($result['url'] ?? '')
    );


    /*
    =====================================================
    GESCHÜTZTE VERZEICHNISSE AUSSCHLIESSEN
    =====================================================

    Befindet sich die Datei oder Seite in einem Verzeichnis,
    dessen .htaccess einen Zugriffsschutz enthält, wird der
    Treffer nicht an das Frontend übermittelt.
    */

    if (
        $url !== ''
        && vnSearchResultIsProtected($url)
    ) {
        continue;
    }


    /*
    =====================================================
    URLS FÜR DIE AUSGABE KORRIGIEREN
    =====================================================

    In der Datenbank kann beispielsweise eine relative URL
    ohne führenden Schrägstrich stehen:

        Projektordner/datei.pdf

    Befindet sich die Suchseite unter:

        /VN-Block-Galerie/Suche.php

    würde der Browser daraus fälschlicherweise machen:

        /VN-Block-Galerie/Projektordner/datei.pdf

    Durch den führenden Schrägstrich wird der Pfad dagegen
    immer ab dem Root-Verzeichnis der Domain aufgerufen:

        /Projektordner/datei.pdf

    Bereits vollständige URLs und Anker werden nicht
    verändert.
    */

    if (
        $url !== ''
        && !str_starts_with($url, '/')
        && !str_starts_with($url, '#')
        && !str_starts_with($url, '?')
        && !preg_match(
            '~^[a-z][a-z0-9+.-]*:~i',
            $url
        )
    ) {
        $url = '/' . ltrim($url, '/');
    }

    $result['url'] = $url;


    /*
    =====================================================
    LEEREN SEITENTITEL ERSETZEN
    =====================================================
    */

    if (
        trim(
            (string)($result['title'] ?? '')
        ) === ''
    ) {
        $result['title'] = $result['url'];
    }


    /*
    =====================================================
    TEXTFRAGMENT NUR AN HTML-SEITEN ANHÄNGEN
    =====================================================
    */

    if (
        ($result['type'] ?? '') === 'page'
        && $firstWord !== ''
        && $result['url'] !== ''
    ) {
        $result['url'] .=
            '#:~:text='
            . rawurlencode($firstWord);
    }


    /*
    =====================================================
    TEXTAUSSCHNITT SICHER AUFBEREITEN
    =====================================================

    Rohe Zeichen wie "<" und ">" werden maskiert.
    Die von SQLite FTS5 erzeugten <mark>-Tags bleiben
    dabei erhalten.
    */

    $snippet = (string)($result['snippet'] ?? '');

    $snippet = str_replace(
        [
            '<mark>',
            '</mark>'
        ],
        [
            "\x01MARKOPEN\x02",
            "\x01MARKCLOSE\x02"
        ],
        $snippet
    );

    $snippet = htmlspecialchars(
        $snippet,
        ENT_QUOTES | ENT_HTML5,
        'UTF-8'
    );

    $snippet = str_replace(
        [
            "\x01MARKOPEN\x02",
            "\x01MARKCLOSE\x02"
        ],
        [
            '<mark>',
            '</mark>'
        ],
        $snippet
    );

    $result['snippet'] = $snippet;


    /*
    Der interne Rangwert wird im Frontend nicht benötigt.
    */

    unset($result['rank']);

    $publicResults[] = $result;
}


/*
=========================================================
TREFFERZAHL UND LIMIT ANWENDEN
=========================================================
*/

$totalCount = count($publicResults);

$results = array_slice(
    $publicResults,
    0,
    $limit
);


/*
=========================================================
ERGEBNISSE ALS JSON AUSGEBEN
=========================================================
*/

vnSearchRespond(
    true,
    '',
    [
        'results' => $results,
        'count'   => count($results),
        'total'   => $totalCount
    ]
);
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Habe da noch eine Frage:

Eigentlich kann man doch, nachdem man mit dien VN-Blöcken in einem Projekt fertig ist und alles am Server hat, die Schalter der VN-Blöcke alle ausschalten - oder. Dann nur bei Änderungen wieder einschalten.

Damit eben nicht immer und immer wieder sämtliche PHP-Dateien aller installierten Anwendungen erneut auf den Server kopiert werden, obwohl man vielleicht nur ein wenig Text auf einer ganz anderen Seite dieses Projektes geändert hat.
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Moin Tommy,

alles gefixt. Alle Anwendungen nun abschaltbar. search.php neu. Testen bitte ;)
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Tommy Herrmann hat geschrieben: Di 4. Aug 2026, 07:02 Habe da noch eine Frage:

Eigentlich kann man doch, nachdem man mit dien VN-Blöcken in einem Projekt fertig ist und alles am Server hat, die Schalter der VN-Blöcke alle ausschalten - oder. Dann nur bei Änderungen wieder einschalten.

Damit eben nicht immer und immer wieder sämtliche PHP-Dateien aller installierten Anwendungen erneut auf den Server kopiert werden, obwohl man vielleicht nur ein wenig Text auf einer ganz anderen Seite dieses Projektes geändert hat.
Ja kannst du oder auch deinstallieren ginge auch - und nur bei änderungen wieder neu importieren. Ginge auch.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Moin,

ja - habe die neueste Version von "VN Suche" soeben installiert und publiziert und dabei die "search.php" mit der neuesten Version von Dir überschrieben.

Alles funktioniert astrein.

Das wird sogar alles richtig angezeigt, wenn man meine Seite auf Englisch umstellt.

Highlight - es werden auch die Inhalte von PDF-Dateien durchsucht.

Habe gerade meine möglichen Ergebnisse von 100 auf 200 erhöht, denn da kommen sehr oft Ergebnisse mit weit über einhundert Fundstellen.

↗️ https://www.mobirise-tutorials.com/VN-B ... .php#Suche


Die beste "Große Suchmaschine", die es bisher für Mobirise gegeben hat. Hut ab :tu:
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Noch ein Fehler bei der Suche (glaube ich).

Gib mal bei mir ein:

VN


Verzeichnisse assets werden durchsucht.jpg


Ich habe ja das Verzeichnis "assets" in der Verzeichnis-Ansicht angehakt, das soll also nicht durchsucht werden. Das scheint aber nur für das Root-Verzeichnis "assets" zu wirken.

Jedes meiner Projekte - also in jedem Projekt-Verzeichnis gibt es doch auch ein Verzeichnis "assets", das wird munter weiter durchsucht.
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Du Witzbold ;)

Deine KI hat doch die search.php umgeschrieben. Hab ich 1:1 so übernommen.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Moin,

das hat mit der search.php nichts zu tun - selber Witzbold (das solltest Du auch wissen) :angst:

Ich habe das Script "db.php" jetzt geändert, denn das steht in diesem Script - nicht im "search.php" (da sieht man den Fehler ja auch ganz klar - nur Root "assets") und sämtliche Dateien neu gescannt. Es sind nun statt 631 ca. 530. Leider werden die Verzeichnisse "assets" immer noch gefunden - auch in dem Verzeichnis was zu dieser Anwendung gehört.

Das weist darauf hin, dass das noch in anderen Scripts bearbeitet wird. Ich weiß aber nicht wo - oder ob der Scan schon fehlerhaft ist, was ich vermute.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Volker,

ich habe jetzt im Ausschluss "assets*" eingegeben - siehe Screenshot - das geht nicht.

Bitte versuche es selbst und trage in der Suche auf meiner Seite "VN" ein, dann siehst Du es doch selbst:

https://www.mobirise-tutorials.com/VN-B ... .php#Suche

Man kann die Links auch klicken und bekommt (wegen Ausschluss) eine leere Seite als Ergebnis. Diese URLs zum Ordner "assets" dürfen erst gar nicht in den Suchergebnissen auftauchen.

Ausschluss auch von assets.jpg


Das muss umprogrammiert werden - nur weiß ich nicht wo überall. Die Datei "db.php" hat mit ChatGPT bereits umprogrammiert, dort hat sie den Fehler sofort erkannt. Meint aber es muss in weiteren Scripts umgeschrieben werden, wahrscheinlich schon beim Scan.
ChatGPT hat geschrieben:
Du musst Volker deshalb noch nicht damit beschäftigen. Poste die PHP-Datei, in der RecursiveDirectoryIterator, scandir oder vnSearchIsProtected( vorkommt. Das ist mit sehr hoher Wahrscheinlichkeit die Datei, in der der Fehler sauber behoben werden kann.

Ich poste jetzt der KI ChatGPT mal die .mbrext-Datei, vielleicht findet sie das dann.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Das ist erschreckend - ChatGPT hat den Fehler in ca. 5 Sekunden aus der gesamten .mbrext-Datei finden können und arbeitet nun daran. Es liegt am Scan.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

ChatGPT hat geschrieben:
Ich habe die komplette .mbrext-Datei entpackt und alle PHP-Dateien geprüft. Der Fehler liegt eindeutig in:

assets/vn-search/admin/scan-folder.php

Dort wurde bislang nur geprüft, ob ein ausgeschlossener Ordner am Anfang des relativen Pfades steht. Deshalb funktionierte:

/assets/

aber nicht:

/VN-Block-Galerie/assets/
/Mobirise-Tutorials/assets/
/Tutorials-3/assets/

Ich habe die vollständige Datei korrigiert. Ein einfacher Ausschluss wie assets gilt nun für jeden Ordner dieses Namens, unabhängig von seiner Verzeichnistiefe.

scan-folder.php

Die Datei gehört auf dem Server hierhin:

/assets/vn-search/admin/scan-folder.php

Die bereits geänderten Dateien db.php und search.php kannst du unverändert lassen.

Nach dem Austausch musst du die schon vorhandenen Suchindex-Einträge erneut löschen und den vollständigen Scan noch einmal starten. Der neue Scanner verhindert nur die erneute Aufnahme; bereits gespeicherte Treffer aus assets verschwinden nicht automatisch.

Danach sollte das angehakte Verzeichnis assets unter anderem alle diese Ordner ausschließen:

/assets/
/VN-Block-Galerie/assets/
/Mobirise-Tutorials/assets/
/Tutorials-3/assets/
/jedes-andere-projekt/assets/

... ich brauch aber länger als ChatGPT :lachtot:
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Mein Gott - was würdest Du nur ohne meine Tests und Überarbeitungen machen :eek:

Jetzt läuft es perfekt - keine "assets" Ordner mehr :tu:

↗️ https://www.mobirise-tutorials.com/VN-B ... .php#Suche

Hier das neue Script "scan-folder.php", nur das muss getauscht werden. Anschließend alle Dateien löschen und den Scan nochmals ganz von vorne machen - also alles neu im Adminbereich. Das Verzeichnis "assets" muss nach wie vor angehakt sein - nur wird es jetzt in allen Projekten ausgeschlossen.


scan-folder.php

Code: Alles auswählen

<?php

declare(strict_types=1);

require __DIR__ . '/auth.php';
vnSearchRequireLogin();
require __DIR__ . '/../db.php';
require __DIR__ . '/../html-helper.php';

header('Content-Type: application/json; charset=utf-8');

function vnRespondScan(bool $success, string $message, array $extra = []): void
{
    http_response_code($success ? 200 : 400);
    echo json_encode(array_merge(['success' => $success, 'message' => $message], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !vnSearchCheckCsrf()) {
    vnRespondScan(false, 'Ungültige Anfrage.');
}

$pdo = vnSearchDb();

// Fehler/Warnungen ab hier sammeln (z.B. open_basedir-Einschraenkungen) -
// muss VOR der Root-Aufloesung installiert werden, da genau dort (beim
// Versuch, einen ueberschriebenen Root ausserhalb des erlaubten Bereichs
// aufzuloesen) solche Warnungen typischerweise zuerst auftreten.
$accessWarnings = [];
set_error_handler(function ($errno, $errstr) use (&$accessWarnings) {
    $accessWarnings[] = $errstr;
    return true;
});

$folderRaw = trim((string)($_POST['folder'] ?? ''));
// Leer = kompletter Server (ab Webroot). Sonst: ab diesem Unterordner,
// jetzt IMMER rekursiv (auch Unterordner des Unterordners werden erfasst).

// Zusaetzliche Dateiendungen (kommagetrennt), z.B. "mbrext, zip" - PDF ist
// immer automatisch mit dabei. Fuer Nicht-PDF-Dateien wird nur der Dateiname
// durchsuchbar gemacht (kein Textinhalt moeglich bei z.B. ZIP-Dateien).

// HARTE Sperrliste: diese Endungen werden NIEMALS erfasst, egal was der
// Admin bei "Zusaetzliche Dateitypen" eintraegt - verhindert, dass aus
// Versehen sensible Dateien (Zugangsdaten, Datenbanken, Zertifikate)
// durchsuchbar/auffindbar gemacht werden.
$hardBlocklist = [
    'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'pht', 'phar',
    'db', 'sqlite', 'sqlite3', 'sql', 'env', 'ini', 'key', 'pem', 'crt',
    'cer', 'pfx', 'p12', 'htaccess', 'htpasswd', 'conf', 'config', 'log',
    'bak', 'backup', 'swp', 'git', 'ssh',
];

// PHP-Seiten sind ein Sonderfall: sie bleiben in der Sperrliste fuer die
// normale Datei-Erfassung (Quellcode wird NIE gelesen), koennen aber optional
// zusaetzlich gefunden und dann sicher per HTTP abgerufen werden (wie beim
// manuellen "URL hinzufuegen" - der Server liefert das fertige HTML, kein
// Quellcode-Zugriff). Nur wenn explizit angehakt.
$includePhpPages = !empty($_POST['includePhpPages']);

$extraExtRaw = trim((string)($_POST['extraExtensions'] ?? ''));
$extraExts = ['pdf', 'html', 'htm'];
if ($extraExtRaw !== '') {
    foreach (explode(',', $extraExtRaw) as $e) {
        $e = strtolower(trim($e, ". \t\n\r\0\x0B"));
        // Nur harmlose Datei-Endungen zulassen (Buchstaben/Zahlen), keine
        // Sonderzeichen die als Glob-Muster missbraucht werden koennten,
        // und niemals etwas von der Sperrliste - selbst wenn explizit angefragt.
        if ($e !== '' && preg_match('/^[a-z0-9]+$/', $e) && !in_array($e, $extraExts, true) && !in_array($e, $hardBlocklist, true)) {
            $extraExts[] = $e;
        }
    }
}

// Website-Wurzel: drei Ebenen ueber assets/vn-search/admin/ liegt der Webroot
// (assets/vn-search/admin/ -> assets/vn-search/ -> assets/ -> Webroot) -
// kann vom Admin ueberschrieben werden (z.B. bei Shared-Hosting mit
// mehreren Projekten im selben Account, siehe vnSearchResolveScanRoot).
$defaultWebRoot = realpath(__DIR__ . '/../../..');
if ($defaultWebRoot === false) {
    vnRespondScan(false, 'Server-Wurzelverzeichnis konnte nicht ermittelt werden.');
}
$webRoot = vnSearchResolveScanRoot($pdo, $defaultWebRoot);

// Falls der Root ausserhalb der aktuellen open_basedir-Beschraenkung liegt,
// versuchen wir sie zur Laufzeit zu erweitern - klappt nicht auf jedem
// Hosting (manche setzen das serverseitig fest, PHP_INI_SYSTEM), ist aber
// einen Versuch wert und schadet nicht, wenn es fehlschlaegt.
$currentBasedir = ini_get('open_basedir');
if ($currentBasedir !== '' && $currentBasedir !== false) {
    @ini_set('open_basedir', $currentBasedir . PATH_SEPARATOR . $webRoot);
}

if ($folderRaw === '') {
    $realRequestedPath = $webRoot;
} else {
    // Pfad-Traversal verhindern: nur innerhalb des Webroots erlauben
    $requestedPath = $webRoot . '/' . ltrim($folderRaw, '/');
    $realRequestedPath = realpath($requestedPath);

    if ($realRequestedPath === false || strpos($realRequestedPath, $webRoot) !== 0) {
        vnRespondScan(false, 'Ordner nicht gefunden oder außerhalb des erlaubten Bereichs.');
    }
    if (!is_dir($realRequestedPath)) {
        vnRespondScan(false, 'Das ist kein Ordner.');
    }
    if (vnSearchIsHtaccessProtected($realRequestedPath)) {
        vnRespondScan(false, 'Dieser Ordner ist per .htaccess passwortgeschützt und wird aus Sicherheitsgründen nie direkt gelesen.');
    }
}

// Ordner, die NIE mit erfasst werden sollen - eigene Datenbank/Uploads nicht
// nochmal sich selbst durchsuchen, sowie uebliche System-/versteckte Ordner.
// Der eigene Ordner wird relativ zum TATSAECHLICH genutzten Root berechnet
// (nicht fest "assets/vn-search"), da der Root ja ueberschrieben sein kann
// und damit auf einer anderen Ebene liegt.
$ownFolderAbs = realpath(__DIR__ . '/..'); // .../assets/vn-search
$ownFolderRel = $ownFolderAbs !== false ? ltrim(str_replace($webRoot, '', $ownFolderAbs), '/') : 'assets/vn-search';
$alwaysExcluded = [$ownFolderRel, '.git', 'node_modules', '.well-known'];

// Zusaetzlich vom Admin im Zahnrad/Panel ausgewaehlte Ordner (nur relevant,
// wenn der komplette Server oder ein Elternordner gescannt wird).
$userExcludedJson = vnSearchGetSetting($pdo, 'excluded_folders', '[]');
$userExcluded = json_decode((string)$userExcludedJson, true);
if (is_array($userExcluded)) {
    foreach ($userExcluded as $uf) {
        $uf = trim((string)$uf, '/');
        if ($uf !== '') {
            $alwaysExcluded[] = $uf;
        }
    }
}

$pdfFiles = [];
$otherFiles = [];
$htmlFiles = [];
$phpFiles = [];

// Geschuetzte Dateimuster laden (z.B. "config.php, *.key") - werden bei
// JEDER gefundenen Datei geprueft, unabhaengig vom Dateityp.
$protectedPatternsJson = vnSearchGetSetting($pdo, 'protected_patterns', '[]');
$protectedPatterns = json_decode((string)$protectedPatternsJson, true);
if (!is_array($protectedPatterns)) {
    $protectedPatterns = [];
}
$protectedPatterns = array_filter(array_map('trim', $protectedPatterns));

/**
 * Prueft, ob ein Ordner per .htaccess passwortgeschuetzt ist (AuthType/
 * Require-Direktiven) - solche Ordner werden beim Scan NIE gelesen, da PHPs
 * direkter Dateizugriff (anders als ein normaler Browser-Aufruf) den
 * .htaccess-Schutz sonst komplett umgehen wuerde.
 */
function vnSearchIsHtaccessProtected(string $dirPath): bool
{
    $htaccessPath = rtrim($dirPath, '/') . '/.htaccess';
    if (!file_exists($htaccessPath) || !is_readable($htaccessPath)) {
        return false;
    }
    $content = @file_get_contents($htaccessPath);
    if ($content === false) {
        return false;
    }
    return (bool)preg_match('/^\s*(AuthType|AuthUserFile|Require\s+valid-user|Require\s+user)/mi', $content);
}

/**
 * Prueft, ob ein relativer Verzeichnispfad durch einen der gespeicherten
 * Ordnerausschluesse erfasst wird.
 *
 * Ausschluesse ohne Schraegstrich, zum Beispiel "assets", gelten fuer
 * jeden gleichnamigen Ordner in beliebiger Verzeichnistiefe:
 *
 *     /assets/
 *     /VN-Block-Galerie/assets/
 *     /Mobirise-Tutorials/assets/
 *
 * Ausschluesse mit Schraegstrich, zum Beispiel "assets/vn-search" oder
 * "Projekt/private", gelten dagegen als konkreter relativer Pfad und
 * schliessen diesen Pfad samt seiner Unterordner aus.
 */
function vnSearchIsExcludedDirectory(string $relativePath, array $excludedFolders): bool
{
    $relativePath = trim(str_replace('\\', '/', $relativePath), '/');

    $cleanPath = preg_replace('~/+~', '/', $relativePath);
    if (is_string($cleanPath)) {
        $relativePath = $cleanPath;
    }

    $pathParts = preg_split(
        '~/+~',
        $relativePath,
        -1,
        PREG_SPLIT_NO_EMPTY
    );

    if (!is_array($pathParts)) {
        $pathParts = [];
    }

    foreach ($excludedFolders as $excludedFolder) {
        $excludedFolder = trim(
            str_replace('\\', '/', (string)$excludedFolder),
            '/'
        );

        if ($excludedFolder === '') {
            continue;
        }

        $cleanExcluded = preg_replace('~/+~', '/', $excludedFolder);
        if (is_string($cleanExcluded)) {
            $excludedFolder = $cleanExcluded;
        }

        /*
         * Ein einfacher Ordnername gilt ueberall im Pfad.
         */
        if (!str_contains($excludedFolder, '/')) {
            foreach ($pathParts as $pathPart) {
                if (strcasecmp($pathPart, $excludedFolder) === 0) {
                    return true;
                }
            }

            continue;
        }

        /*
         * Ein konkreter relativer Pfad gilt fuer genau diesen Ordner und
         * alle darunterliegenden Verzeichnisse.
         */
        if (
            strcasecmp($relativePath, $excludedFolder) === 0
            || strncasecmp(
                $relativePath,
                $excludedFolder . '/',
                strlen($excludedFolder) + 1
            ) === 0
        ) {
            return true;
        }
    }

    return false;
}

try {
    $dirIterator = new RecursiveDirectoryIterator($realRequestedPath, FilesystemIterator::SKIP_DOTS);

    $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, function ($current, $key, $iterator) use ($webRoot, $alwaysExcluded) {
        if ($current->isDir()) {
            $relPath = ltrim(str_replace($webRoot, '', $current->getPathname()), '/\\');
            $baseName = $current->getBasename();

            // versteckte Ordner grundsaetzlich nicht betreten
            if ($baseName !== '' && $baseName[0] === '.') {
                return false;
            }

            // Vom Administrator ausgewaehlte einfache Ordnernamen wie
            // "assets" werden jetzt in JEDER Verzeichnistiefe erkannt.
            // Konkrete Pfade wie "assets/vn-search" bleiben ebenfalls
            // moeglich und gelten fuer diesen Pfad samt Unterordnern.
            if (vnSearchIsExcludedDirectory($relPath, $alwaysExcluded)) {
                return false;
            }

            // Passwortgeschuetzte Ordner (.htaccess) nie betreten - PHPs
            // direkter Dateizugriff wuerde den Passwortschutz sonst umgehen.
            if (vnSearchIsHtaccessProtected($current->getPathname())) {
                return false;
            }
        }
        return true;
    });

    $iterator = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::LEAVES_ONLY);
    $iterator->setMaxDepth(-1);
} catch (\Throwable $e) {
    restore_error_handler();
    vnRespondScan(false, 'Ordner konnte nicht gelesen werden: ' . $e->getMessage());
}

foreach ($iterator as $item) {
    try {
        if (!$item->isFile()) {
            continue;
        }
    } catch (\Throwable $e) {
        // Einzelner Eintrag nicht lesbar (z.B. kaputter Symlink, Rechteproblem) -
        // ueberspringen statt den kompletten Scan abzubrechen.
        $accessWarnings[] = $e->getMessage();
        continue;
    }

    $absPath = $item->getPathname();
    $ext = strtolower(pathinfo($absPath, PATHINFO_EXTENSION));

    if (vnSearchIsProtected($absPath, $webRoot, $protectedPatterns)) {
        continue;
    }

    if ($includePhpPages && $ext === 'php') {
        $phpFiles[] = $absPath;
        continue;
    }

    if (!in_array($ext, $extraExts, true)) {
        continue;
    }

    if ($ext === 'pdf') {
        $pdfFiles[] = $absPath;
    } elseif ($ext === 'html' || $ext === 'htm') {
        $htmlFiles[] = $absPath;
    } else {
        $otherFiles[] = $absPath;
    }
}

restore_error_handler();

if (empty($pdfFiles) && empty($otherFiles) && empty($htmlFiles) && empty($phpFiles)) {
    $msg = 'Keine passenden Dateien gefunden.';
    if (!empty($accessWarnings)) {
        // Vermutlich open_basedir-Einschraenkung oder Rechteproblem - das
        // ist auf manchem Shared-Hosting (z.B. IONOS) so konfiguriert, dass
        // PHP nur innerhalb eines bestimmten Ordners lesen darf, auch wenn
        // der Pfad selbst (realpath) noch aufgeloest werden kann.
        $msg .= ' Möglicher Grund: Zugriffsbeschränkung deines Servers (z.B. "open_basedir") verhindert das Lesen außerhalb deines Website-Ordners. Technische Details: ' . implode(' | ', array_slice(array_unique($accessWarnings), 0, 3));
    }
    vnRespondScan(true, $msg, ['found' => 0, 'new' => 0, 'accessWarnings' => array_values(array_unique($accessWarnings))]);
}

$existing = $pdo->query('SELECT stored_filename FROM pdf_files')->fetchAll(PDO::FETCH_COLUMN);
$existingSet = array_flip($existing);

$newCount = 0;

// --- PDFs: wie bisher zur spaeteren Text-Indexierung vormerken ---
$insPdf = $pdo->prepare('INSERT INTO pdf_files (original_name, stored_filename, filesize, source, file_ext, indexed_at) VALUES (:o, :s, :f, "scanned", "pdf", NULL)');

foreach ($pdfFiles as $filePath) {
    $relativePath = ltrim(str_replace($webRoot, '', $filePath), '/');
    if (isset($existingSet[$relativePath])) {
        continue;
    }
    $insPdf->execute(['o' => basename($filePath), 's' => $relativePath, 'f' => (int)filesize($filePath)]);
    $newCount++;
}

// --- Andere Dateitypen: kein Textinhalt moeglich, direkt anhand des
//     Dateinamens indexieren (kein Umweg ueber die Batch-Verarbeitung noetig) ---
$insOther = $pdo->prepare('INSERT INTO pdf_files (original_name, stored_filename, filesize, source, file_ext, indexed_at, fts_rowid) VALUES (:o, :s, :f, "scanned", :ext, :ia, :fr)');

foreach ($otherFiles as $filePath) {
    $relativePath = ltrim(str_replace($webRoot, '', $filePath), '/');
    if (isset($existingSet[$relativePath])) {
        continue;
    }

    $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $baseName = pathinfo($filePath, PATHINFO_FILENAME);
    // Durchsuchbarer "Inhalt" ist hier einfach der (lesbarer gemachte) Dateiname -
    // z.B. "vn-formrender-v80" statt nur "vn-formrender-v80.mbrext"
    $searchableContent = str_replace(['-', '_'], ' ', $baseName) . ' ' . $ext;

    $ftsRowid = vnSearchIndexUpsert($pdo, null, basename($filePath), $searchableContent, '/' . $relativePath, 'file');

    $insOther->execute([
        'o' => basename($filePath),
        's' => $relativePath,
        'f' => (int)filesize($filePath),
        'ext' => $ext,
        'ia' => time(),
        'fr' => $ftsRowid,
    ]);
    $newCount++;
}

$totalFound = count($pdfFiles) + count($otherFiles) + count($htmlFiles) + count($phpFiles);

// --- HTML-Dateien: echte Textextraktion (Header/Footer/Nav werden
//     rausgefiltert), landen bei "Seiten" statt bei den PDF-Dateien, da es
//     sich ja um echte Website-Seiten handelt (z.B. andere Projekte in
//     Unterordnern ohne eigene Sitemap-Anbindung). Da lokal von der
//     Festplatte gelesen wird (kein HTTP-Request noetig), passiert das
//     sofort, ohne Umweg ueber die Batch-Verarbeitung. ---
$existingPages = $pdo->query('SELECT url FROM indexed_pages')->fetchAll(PDO::FETCH_COLUMN);
$existingPagesSet = [];
foreach ($existingPages as $existingUrl) {
    $existingPagesSet[vnSearchNormalizeUrl($existingUrl)] = true;
}

$insPage = $pdo->prepare('INSERT INTO indexed_pages (url, title, indexed_at, fts_rowid) VALUES (:u, :t, :ia, :fr)');

foreach ($htmlFiles as $filePath) {
    $relativePath = ltrim(str_replace($webRoot, '', $filePath), '/');
    // Als URL-Pfad verwenden wir den relativen Server-Pfad mit fuehrendem
    // Slash - funktioniert fuer den Link im Suchergebnis genauso wie eine
    // ganz normale Seiten-URL.
    $urlPath = '/' . $relativePath;
    $normalizedUrlPath = vnSearchNormalizeUrl($urlPath);

    if (isset($existingPagesSet[$normalizedUrlPath])) {
        continue;
    }

    $html = @file_get_contents($filePath);
    if ($html === false) {
        continue;
    }

    [$title, $text] = vnSearchExtractPageContent($html);
    if ($title === '') {
        $title = basename($filePath);
    }

    $ftsRowid = vnSearchIndexUpsert($pdo, null, $title, $text, $urlPath, 'page');

    $insPage->execute(['u' => $urlPath, 't' => $title, 'ia' => time(), 'fr' => $ftsRowid]);
    $existingPagesSet[$normalizedUrlPath] = true;
    $newCount++;
}

// --- PHP-Seiten: werden NIE direkt von der Festplatte gelesen (Sicherheit -
//     koennten Zugangsdaten im Code enthalten). Stattdessen genau wie beim
//     manuellen "URL hinzufuegen" nur vorgemerkt und dann per HTTP abgerufen
//     (macht die bereits vorhandene Seiten-Batch-Verarbeitung) - das kann
//     etwas dauern, deshalb hier nur die Warteliste befuellen statt sofort
//     abzurufen. ---
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$ownHost = $_SERVER['HTTP_HOST'] ?? '';
$phpQueued = 0;

foreach ($phpFiles as $filePath) {
    $relativePath = ltrim(str_replace($webRoot, '', $filePath), '/');
    $fullUrl = $scheme . '://' . $ownHost . '/' . $relativePath;
    $normalizedFullUrl = vnSearchNormalizeUrl($fullUrl);

    if (isset($existingPagesSet[$normalizedFullUrl])) {
        continue;
    }

    $insPage->execute(['u' => $fullUrl, 't' => null, 'ia' => null, 'fr' => null]);
    $existingPagesSet[$normalizedFullUrl] = true;
    $phpQueued++;
    $newCount++;
}

$msg = $newCount . ' neue Datei(en) gefunden (PDFs werden noch nach Text durchsucht, HTML-Seiten sind sofort mit echtem Inhalt durchsuchbar, andere Dateien nur per Dateiname).';
if ($phpQueued > 0) {
    $msg .= ' ' . $phpQueued . ' PHP-Seite(n) werden im Hintergrund per HTTP abgerufen und durchsuchbar gemacht.';
}

vnRespondScan(true, $msg, [
    'found' => $totalFound,
    'new' => $newCount,
    'phpQueued' => $phpQueued,
]);

Nur zu Deiner Information
:

Die Menüleiste wird nach wie vor durchsucht und mit einbezogen - aber ist egal - vielleicht sogar ganz gut, nur weil Du behauptet hast, dass die Menüleiste und der Footer ausgeschlossen sind.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Volker,

... nächste Fehler :eek:

Was mir auffällt und was nicht richtig ist:

Gebe ich in der Suche ein:

.pdf

kommt zum Beispiel auch die Datei "Formular.pdf".

Gebe ich dagegen den Dateinamen komplett ein:

Formular.pdf

ein - wird nichts gefunden :angst:



ChatGPT hat geschrieben:
Korrektur

Sonderzeichen wie Punkt, Bindestrich oder Schrägstrich müssen als Worttrenner behandelt werden, statt sie einfach ersatzlos zu entfernen.


Hier die neue Datei - bitte ersetzen:

db.php

Code: Alles auswählen

<?php

declare(strict_types=1);

/**
 * Gemeinsame Datenbank-Anbindung für alle vn-search-Skripte.
 *
 * SQLite mit FTS5:
 * Die Volltextsuche ist direkt in SQLite integriert. Ein externer
 * Suchserver wird nicht benötigt.
 */
function vnSearchDb(): PDO
{
    static $pdo = null;

    if ($pdo !== null) {
        return $pdo;
    }

    $dbDir = __DIR__ . '/data';

    if (!is_dir($dbDir)) {
        mkdir($dbDir, 0755, true);
        file_put_contents($dbDir . '/.htaccess', "Deny from all\n");
    }

    $dbPath = $dbDir . '/search.sqlite';
    $isNew = !file_exists($dbPath);

    $pdo = new PDO('sqlite:' . $dbPath);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

    if ($isNew) {
        vnSearchInitSchema($pdo);
    }

    vnSearchMigrate($pdo);

    return $pdo;
}

/**
 * Leichte Migration für bereits bestehende Installationen.
 */
function vnSearchMigrate(PDO $pdo): void
{
    $columns = $pdo->query('PRAGMA table_info(pdf_files)')->fetchAll();
    $columnNames = array_column($columns, 'name');

    if (!in_array('file_ext', $columnNames, true)) {
        $pdo->exec('ALTER TABLE pdf_files ADD COLUMN file_ext TEXT');
    }

    $rows = $pdo->query(
        'SELECT id, original_name
         FROM pdf_files
         WHERE file_ext IS NULL OR file_ext = ""'
    )->fetchAll();

    if (!empty($rows)) {
        $update = $pdo->prepare(
            'UPDATE pdf_files
             SET file_ext = :ext
             WHERE id = :id'
        );

        foreach ($rows as $row) {
            $extension = strtolower(
                pathinfo((string)$row['original_name'], PATHINFO_EXTENSION)
            );

            if ($extension === '') {
                $extension = 'pdf';
            }

            $update->execute([
                'ext' => $extension,
                'id'  => $row['id']
            ]);
        }
    }
}

/**
 * Erstellt das Datenbankschema bei einer neuen Installation.
 */
function vnSearchInitSchema(PDO $pdo): void
{
    $pdo->exec(
        'CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
            title,
            content,
            url UNINDEXED,
            type UNINDEXED,
            tokenize="unicode61"
        )'
    );

    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS pdf_files (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            original_name TEXT NOT NULL,
            stored_filename TEXT NOT NULL,
            filesize INTEGER NOT NULL DEFAULT 0,
            source TEXT NOT NULL DEFAULT "uploaded",
            file_ext TEXT,
            indexed_at INTEGER,
            fts_rowid INTEGER,
            error TEXT
        )'
    );

    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS indexed_pages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            url TEXT NOT NULL UNIQUE,
            title TEXT,
            indexed_at INTEGER,
            fts_rowid INTEGER
        )'
    );

    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS admin_users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT NOT NULL UNIQUE,
            password_hash TEXT NOT NULL
        )'
    );

    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS settings (
            key TEXT PRIMARY KEY,
            value TEXT
        )'
    );
}

/**
 * Liest eine Einstellung aus der Datenbank.
 */
function vnSearchGetSetting(PDO $pdo, string $key, $default = null)
{
    $statement = $pdo->prepare(
        'SELECT value
         FROM settings
         WHERE key = :key'
    );

    $statement->execute(['key' => $key]);
    $value = $statement->fetchColumn();

    return $value === false ? $default : $value;
}

/**
 * Speichert oder aktualisiert eine Einstellung.
 */
function vnSearchSetSetting(PDO $pdo, string $key, string $value): void
{
    $statement = $pdo->prepare(
        'INSERT INTO settings (key, value)
         VALUES (:key, :value)
         ON CONFLICT(key)
         DO UPDATE SET value = :value'
    );

    $statement->execute([
        'key'   => $key,
        'value' => $value
    ]);
}

/**
 * Normalisiert eine URL oder einen Pfad für den Duplikat-Abgleich.
 */
function vnSearchNormalizeUrl(string $url): string
{
    $path = parse_url($url, PHP_URL_PATH);

    if (!is_string($path) || $path === '') {
        $path = $url;
    }

    $path = str_replace('\\', '/', $path);
    $path = rtrim($path, '/');

    $normalized = preg_replace(
        '#/(index|default)\.(html?|php)$#i',
        '',
        $path
    );

    if (is_string($normalized)) {
        $path = $normalized;
    }

    return strtolower($path);
}

/**
 * Prüft, ob ein Dateiname oder Pfad einem ausgeschlossenen
 * beziehungsweise geschützten Muster entspricht.
 *
 * Es werden geprüft:
 * 1. der reine Datei- oder Ordnername,
 * 2. der vollständige relative Pfad,
 * 3. jeder einzelne Bestandteil des relativen Pfades.
 */
function vnSearchIsProtected(
    string $pathOrUrl,
    string $webRoot,
    array $patterns
): bool {
    if (empty($patterns)) {
        return false;
    }

    $parsedPath = parse_url($pathOrUrl, PHP_URL_PATH);

    if (!is_string($parsedPath) || $parsedPath === '') {
        $parsedPath = $pathOrUrl;
    }

    $normalizedPath = rawurldecode(
        str_replace('\\', '/', $parsedPath)
    );

    $normalizedWebRoot = rawurldecode(
        str_replace('\\', '/', $webRoot)
    );

    $normalizedWebRoot = rtrim($normalizedWebRoot, '/');
    $relativePath = $normalizedPath;

    if (
        $normalizedWebRoot !== ''
        && strncasecmp(
            $normalizedPath,
            $normalizedWebRoot,
            strlen($normalizedWebRoot)
        ) === 0
    ) {
        $relativePath = substr(
            $normalizedPath,
            strlen($normalizedWebRoot)
        );
    }

    $relativePath = ltrim($relativePath, '/');

    $cleanRelativePath = preg_replace(
        '~/+~',
        '/',
        $relativePath
    );

    if (is_string($cleanRelativePath)) {
        $relativePath = $cleanRelativePath;
    }

    $baseName = basename(rtrim($normalizedPath, '/'));

    $pathParts = preg_split(
        '~/+~',
        trim($relativePath, '/'),
        -1,
        PREG_SPLIT_NO_EMPTY
    );

    if (!is_array($pathParts)) {
        $pathParts = [];
    }

    foreach ($patterns as $pattern) {
        $pattern = trim(
            str_replace('\\', '/', (string)$pattern)
        );

        if ($pattern === '') {
            continue;
        }

        $normalizedPattern = ltrim($pattern, '/');

        if ($normalizedPattern === '') {
            continue;
        }

        if (fnmatch($normalizedPattern, $baseName, FNM_CASEFOLD)) {
            return true;
        }

        if (fnmatch($normalizedPattern, $relativePath, FNM_CASEFOLD)) {
            return true;
        }

        if (
            str_contains($normalizedPattern, '/')
            && fnmatch(
                '*/' . $normalizedPattern,
                $relativePath,
                FNM_CASEFOLD
            )
        ) {
            return true;
        }

        foreach ($pathParts as $pathPart) {
            if (
                fnmatch(
                    $normalizedPattern,
                    $pathPart,
                    FNM_CASEFOLD
                )
            ) {
                return true;
            }
        }
    }

    return false;
}

/**
 * Ermittelt den tatsächlich zu verwendenden Scan-Wurzelordner.
 */
function vnSearchResolveScanRoot(
    PDO $pdo,
    string $defaultWebRoot
): string {
    $override = trim(
        (string)vnSearchGetSetting(
            $pdo,
            'scan_root_override',
            ''
        )
    );

    if ($override === '') {
        return $defaultWebRoot;
    }

    $isAbsoluteUnixPath = str_starts_with($override, '/');

    $isAbsoluteWindowsPath = preg_match(
        '#^[A-Za-z]:[\\\\/]#',
        $override
    ) === 1;

    if ($isAbsoluteUnixPath || $isAbsoluteWindowsPath) {
        $candidate = $override;
    } else {
        $candidate =
            rtrim($defaultWebRoot, '/\\')
            . DIRECTORY_SEPARATOR
            . $override;
    }

    $resolved = realpath($candidate);

    if ($resolved !== false && is_dir($resolved)) {
        return $resolved;
    }

    return $defaultWebRoot;
}

/**
 * Fügt einen Eintrag in den Volltextindex ein.
 */
function vnSearchIndexUpsert(
    PDO $pdo,
    ?int $oldFtsRowid,
    string $title,
    string $content,
    string $url,
    string $type
): int {
    if ($oldFtsRowid !== null) {
        $delete = $pdo->prepare(
            'DELETE FROM search_index
             WHERE rowid = :rowid'
        );

        $delete->execute([
            'rowid' => $oldFtsRowid
        ]);
    }

    $insert = $pdo->prepare(
        'INSERT INTO search_index (
            title,
            content,
            url,
            type
        )
        VALUES (
            :title,
            :content,
            :url,
            :type
        )'
    );

    $insert->execute([
        'title'   => $title,
        'content' => $content,
        'url'     => $url,
        'type'    => $type
    ]);

    return (int)$pdo->lastInsertId();
}

/**
 * Löscht einen Eintrag anhand seiner FTS-rowid.
 */
function vnSearchIndexDelete(PDO $pdo, ?int $ftsRowid): void
{
    if ($ftsRowid === null) {
        return;
    }

    $delete = $pdo->prepare(
        'DELETE FROM search_index
         WHERE rowid = :rowid'
    );

    $delete->execute([
        'rowid' => $ftsRowid
    ]);
}

/**
 * Erstellt aus einer Benutzereingabe eine sichere
 * SQLite-FTS5-Prefix-Suchanfrage.
 *
 * Sonderzeichen werden als Worttrenner behandelt.
 *
 * Beispiel:
 * Formular.pdf
 *
 * wird zu:
 * "Formular"* AND "pdf"*
 */
function vnSearchBuildFtsQuery(string $rawQuery): string
{
    $matchResult = preg_match_all(
        '/[\p{L}\p{N}]+/u',
        trim($rawQuery),
        $matches
    );

    if (
        $matchResult === false
        || empty($matches[0])
    ) {
        return '';
    }

    $parts = [];

    foreach ($matches[0] as $word) {
        $word = trim((string)$word);

        if ($word === '') {
            continue;
        }

        $normalizedWord = function_exists('mb_strtolower')
            ? mb_strtolower($word, 'UTF-8')
            : strtolower($word);

        if (isset($parts[$normalizedWord])) {
            continue;
        }

        $parts[$normalizedWord] =
            '"'
            . str_replace('"', '""', $word)
            . '"*';
    }

    return implode(
        ' AND ',
        array_values($parts)
    );
}
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

Volker,

Bitte ersetze die beiden PHP-Dateien, wie oben gepostet:

db.php

und

scan-folder.php

und erstelle die .mbrext-Datei neu zum Testen und melde Dich dann, damit ich diese neu importieren kann. Ich denke mal, dass es das dann auch sein sollte und keine weiteren Fehler mehr auftreten werden.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9339
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Tommy Herrmann »

... wo bist Du? Hast Du die mbrext schon überarbeitet? Ich kann dann erst morgen früh wieder gucken.
Volker
Moderator
Moderator
Beiträge: 3334
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Suche - Mobirise Block ( Komplett System)

Ungelesener Beitrag von Volker »

Mach ich morgen früh Tommy,
hab gerade auch ein bisschen Stress ;)
Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 4 Gäste