Kaputte Suche im Block Editor reparieren

Vor kurzem suchte ich nach einem Artikel im Blog von meinem sehr geschätzten Blogger-Kollegen Bernhard. Es ging um eine zusätzliche Sprache im Syntaxhighlighting-Plugin, was wir beide nutzen. Also suchte ich nach Syntaxhighlight in seinem Blog … aber ich fand viel zu viele Treffer, die das Suchwort gar nicht enthielten. Das erinnerte mich an etwas.

Da war doch was? Stimmt. Schon 2019 auf dem WordCamp Osnabrück hatte ich in meinem Talk zu diversen UX-Problemen im Block-Editor auf die „polluted search“ hingewiesen:

Gutenberg speichert alle Blöcke mit HTML-Kommentaren im post content ab. Dadurch werden Suchen nach “paragraph” oder “image” ziemlich nutzlos, da sie für fast jede Seite/jeden Beitrag nun ein Ergebnis liefern.
Das Problem existiert eigentlich schon immer, aber bei HTML-Befehlen ist das Problem nicht so schlimm (paragraph -> p, image -> img).
Und es kommen immer mehr Blöcke dazu, das Problem nimmt also zu.

Quelle

Ich hatte dazu auch ein Issue für Gutenberg geöffnet (#10247), aber das Problem gab es natürlich schon: #3739

Leider schloss Gary Pendergast das Issue recht schnell mit folgendem Kommentar:

Unfortunately, this is a known issue in WordPress core – in a vanilla WordPress install, if you search for „table“, you’ll get results including table tags. MySQL’s string searching isn’t capable of dealing with this kind of contextual parsing.

If you require 100% accurate search results, the best option is to use a dedicated search engine, like Elasticsearch. There are also Elasticsearch services available within the WordPress world, if setting up a dedicated search server is not an option.

Quelle

Auch der Kommentar von Daniel Bachhuber hat daran nichts geändert:

Given Gutenberg blocks will add substantially more „hidden“ strings, I wonder how much larger of a problem this will become. It’d be interesting to do some analysis comparing an English-language dictionary to partial string matches with Gutenberg blocks.

Halten wir also fest: Es ist ein bekanntes Problem. Passiert bei HTML auch. Und die vorgeschlagene Lösung ist ein eigener Elasticsearch-Server, was für einen Großteil der User wohl kaum eine reale Möglichkeit ist …

An diesem Problem überlege ich jetzt also schon seit drei Jahren herum und da ich aus irgendeinem Grund mal wieder daran dachte, suchte ich mal wieder nach einer Lösung und kam auf ein Plugin, was das ursprüngliche Problem (Suche wird durch HTML-Tags „polluted“) tatsächlich löst: Search Ignore HTML Tags

Das Plugin nutzt vor allem zwei Techniken. Zum einen den sehr mächtigen posts_where-Filter und zum anderen eine negierte regular expression via NOT REGEXP.

Hier der Code als Plugin (UPDATE: Der Code funktioniert so nicht!):

<?php
/**
 * Plugin Name: Ignore block name in search
 * Description: Updated the native search to ignore block editor comments
 * Version: 1.0
 * Author: Torsten Landsiedel
 * License: GPL2
 */

/*
Based on "Search Ignore HTML Tags" by Pramod Sivadas
wordpress.org/plugins/wp-search-ignore-html-tags/
*/

/**
 * Modify search query to ignore comments
 *
 * @param  [string] $where The WHERE clause of the query.
 * @return [string]        Modified WHERE clause which ignores comments via regular expression
 */
function update_search_query( $where ) {
	if ( is_search() ) {
		global $wpdb;
		$query = get_search_query();
		$query = $wpdb->esc_like( $query );

		$where .= " AND {$wpdb->posts}.post_content NOT REGEXP '\<\!\-\-.*$query.*\-\-\>' ";
	}
	return $where;
}

add_filter( 'posts_where', 'update_search_query' );

Wir hängen eine Funktion namens update_search_query an den posts_where-Filter. Darin verändern wir die Abfrage nur, wenn es sich um eine Suche handelt (is_search). Dann holen wir uns das globale Datenbankobjekt ($wpdb) und den Such-Query. Zur Sicherheit wird der Query escaped und um eine weitere Bedingung ergänzt.

Hier ist jetzt das Herzstück dieses kleinen Plugins. Die zusätzliche Bedingung ist, dass der Suchbegriff ($query) nicht durch die regular expression gefunden wird. Wobei diese regular expression die Suche nach dem Begriff innerhalb von Kommentaren ist, also zwischen <!-- und --> steht.

Im Original war die regular expression \<{1}.*$query.*\>{1}, womit nur Texte innerhalb von < und > erfasst wurden.

Das Plugin läuft nun bei mir und zeigt für die Suche nach syntaxhighlight tatsächlich nur noch den einen Beitrag (bzw. bald auch diesen hier), wo das Wort wirklich im Text steht.

Das Plugin von Pramod Sivadas hat damals leider kaum jemanden interessiert, wahrscheinlich weil das Problem mit HTML-Tags nicht so schlimm war. Es hat nur eine 1.0-Version, nur einen Thread im Forum und ist seit 8 Jahren nicht mehr angefasst worden. Daher fehlt eine breitere Testung der Technik. Könnte es Probleme geben?

Freue mich über Feedback zu möglichen Problemen, edge cases, und dergleichen in den Kommentaren!

Update 1: Das Plugin ist jetzt auch im offiziellen Plugin-Verzeichnis und auf GitHub!

Update 2: Leider habe ich die Funktionsweise von MySQL falsch verstanden. Die Abfrage sorgt dafür, dass eine zusätzliche Bedingung gelten muss. Diese lautet, dass der Suchbegriff nicht in einem Kommentar stehen darf. Das bedeutet, dass eine Suche nach „Paragraph“ ungünstigerweise auch dann nichts zurückliefert, wenn „Paragraph“ im Inhalt steht, sobald der String in einem HTML-Kommentar auftaucht. Also auch keine Lösung für das Problem.

Eine Lösung könnte REGEXP_REPLACE sein, aber das ist erst mit MariaDB 10.0.5 bzw. MySQL 8.0.4 verfügbar und noch habe ich das leider nicht zum Laufen bekommen.

Danke an @ravishaheshan für den Hinweis!

Update 3: Für 8.0.4+ und MariaDB 10.0.5+ gibt es jetzt die funktionierende Lösung in meinem Plugin!

6 Antworten auf Kaputte Suche im Block Editor reparieren

  1. Der Code scheint wirklich gut zu funktionieren. Allerdings braucht, es glaube ich, die vielen Escape-Zeichen nicht. Es sollte also auch mit '<!--.*$query.*-->' funktionieren.

    Eine andere Kleinigkeit, die du ändern solltest: die zusätzliche Where-Clause wird so an alle Queries auf der Suchseite angehängt. Das ist natürlich zum einen unnötig und führt zum anderen dazu, dass manche Queries kaputt gehen, wenn wp_posts nicht gejoined ist. Besser also einen „Early Return“ machen, wenn es nicht die Main-Query ist.

    Bei mir würde die Funktion im Ergebnis dann wie folgt aussehen:

    /**
     * Modify search query to ignore the search term in HTML comments.
     *
     * @param string   $where The WHERE clause of the query.
     * @param WP_Query $query The WP_Query instance (passed by reference).
     *
     * @return string The modified WHERE clause.
     */
    function tl_update_search_query( $where, $query ) {
    	if ( ! is_search() || ! $query->is_main_query() ) {
    		return $where;
    	}
    
    	global $wpdb;
    	$search_query = get_search_query();
    	$search_query = $wpdb->esc_like( $search_query );
    
    	$where .= " AND {$wpdb->posts}.post_content NOT REGEXP '<!--.*$search_query.*-->' ";
    
    	return $where;
    }
    add_filter( 'posts_where', 'tl_update_search_query', 10, 2 );
    
  2. Gibt es eigentlich hierzu auch ein Core Ticket und könnte man vielleicht deine Lösung als Patch vorschlagen? Vielleicht auch inkl. der Bedingung zur Ignorierung von HTML Tag Namen?

  3. Hallo Torsten und Bernhard,
    ich liebe so kleine Snippets mit großer Wirkung.
    Vielen Dank dafür.

    Jochen

  4. Da die Lösung eine höhere Datenbank-Version erfordert als WordPress ist die Lösung auf „maybelater“ gestellt. Hier das Trac-Ticket dazu:
    https://core.trac.wordpress.org/ticket/56294

    Achtung an alle: Der Code oben ist falsch und funktioniert nicht wie gewünscht. Die funktionierende Lösung findet ihr in meinem Plugin: https://github.com/Zodiac1978/wp-search-ignore-block-names

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert