LP#1923225: have search highlighting stored procedure do HTML-escaping
authorGalen Charlton <gmc@equinoxinitiative.org>
Wed, 14 Apr 2021 18:30:39 +0000 (14:30 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Tue, 25 May 2021 14:34:14 +0000 (10:34 -0400)
This patch builds on the previous work to have the stored procedures
that produced highlighted and unhighlighted versions of display
attributes HTML-escape the source values, then adjusts the TPAC
and Bootstrap templates to avoid double-escaping.

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

Open-ILS/src/sql/Pg/300.schema.staged_search.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.search_highlight_escape_html.sql [new file with mode: 0644]
Open-ILS/src/templates-bootstrap/opac/parts/record/subjects.tt2
Open-ILS/src/templates-bootstrap/opac/parts/record/summary.tt2

index 57bc7a4..eba7b96 100644 (file)
@@ -1385,7 +1385,7 @@ BEGIN
         SELECT  de.id,
                 de.source,
                 de.field,
-                de.value AS value,
+                evergreen.escape_for_html(de.value) AS value,
                 ts_headline(
                     ts_config::REGCONFIG,
                     evergreen.escape_for_html(de.value),
@@ -1462,8 +1462,8 @@ BEGIN
         SELECT  id,
                 source,
                 field,
-                value,
-                value AS highlight
+                evergreen.escape_for_html(value) AS value,
+                evergreen.escape_for_html(value) AS highlight
           FROM  metabib.display_entry
           WHERE source = rid
                 AND NOT (field = ANY (seen));
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.search_highlight_escape_html.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.search_highlight_escape_html.sql
new file mode 100644 (file)
index 0000000..14f7788
--- /dev/null
@@ -0,0 +1,118 @@
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE OR REPLACE FUNCTION search.highlight_display_fields_impl(
+    rid         BIGINT,
+    tsq         TEXT,
+    field_list  INT[] DEFAULT '{}'::INT[],
+    css_class   TEXT DEFAULT 'oils_SH',
+    hl_all      BOOL DEFAULT TRUE,
+    minwords    INT DEFAULT 5,
+    maxwords    INT DEFAULT 25,
+    shortwords  INT DEFAULT 0,
+    maxfrags    INT DEFAULT 0,
+    delimiter   TEXT DEFAULT ' ... '
+) RETURNS SETOF search.highlight_result AS $f$
+DECLARE
+    opts            TEXT := '';
+    v_css_class     TEXT := css_class;
+    v_delimiter     TEXT := delimiter;
+    v_field_list    INT[] := field_list;
+    hl_query        TEXT;
+BEGIN
+    IF v_delimiter LIKE $$%'%$$ OR v_delimiter LIKE '%"%' THEN --"
+        v_delimiter := ' ... ';
+    END IF;
+
+    IF NOT hl_all THEN
+        opts := opts || 'MinWords=' || minwords;
+        opts := opts || ', MaxWords=' || maxwords;
+        opts := opts || ', ShortWords=' || shortwords;
+        opts := opts || ', MaxFragments=' || maxfrags;
+        opts := opts || ', FragmentDelimiter="' || delimiter || '"';
+    ELSE
+        opts := opts || 'HighlightAll=TRUE';
+    END IF;
+
+    IF v_css_class LIKE $$%'%$$ OR v_css_class LIKE '%"%' THEN -- "
+        v_css_class := 'oils_SH';
+    END IF;
+
+    opts := opts || $$, StopSel=</b>, StartSel="<b class='$$ || v_css_class; -- "
+
+    IF v_field_list = '{}'::INT[] THEN
+        SELECT ARRAY_AGG(id) INTO v_field_list FROM config.metabib_field WHERE display_field;
+    END IF;
+
+    hl_query := $$
+        SELECT  de.id,
+                de.source,
+                de.field,
+                evergreen.escape_for_html(de.value) AS value,
+                ts_headline(
+                    ts_config::REGCONFIG,
+                    evergreen.escape_for_html(de.value),
+                    $$ || quote_literal(tsq) || $$,
+                    $1 || ' ' || mf.field_class || ' ' || mf.name || $xx$'>"$xx$ -- "'
+                ) AS highlight
+          FROM  metabib.display_entry de
+                JOIN config.metabib_field mf ON (mf.id = de.field)
+                JOIN search.best_tsconfig t ON (t.id = de.field)
+          WHERE de.source = $2
+                AND field = ANY ($3)
+          ORDER BY de.id;$$;
+
+    RETURN QUERY EXECUTE hl_query USING opts, rid, v_field_list;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION search.highlight_display_fields(
+    rid         BIGINT,
+    tsq_map     TEXT, -- { '(a | b) & c' => '1,2,3,4', ...}
+    css_class   TEXT DEFAULT 'oils_SH',
+    hl_all      BOOL DEFAULT TRUE,
+    minwords    INT DEFAULT 5,
+    maxwords    INT DEFAULT 25,
+    shortwords  INT DEFAULT 0,
+    maxfrags    INT DEFAULT 0,
+    delimiter   TEXT DEFAULT ' ... '
+) RETURNS SETOF search.highlight_result AS $f$
+DECLARE
+    tsq_hstore  TEXT;
+    tsq         TEXT;
+    fields      TEXT;
+    afields     INT[];
+    seen        INT[];
+BEGIN
+    IF (tsq_map ILIKE 'hstore%') THEN
+        EXECUTE 'SELECT ' || tsq_map INTO tsq_hstore;
+    ELSE
+        tsq_hstore := tsq_map::HSTORE;
+    END IF;
+
+    FOR tsq, fields IN SELECT key, value FROM each(tsq_hstore::HSTORE) LOOP
+        SELECT  ARRAY_AGG(unnest::INT) INTO afields
+          FROM  unnest(regexp_split_to_array(fields,','));
+        seen := seen || afields;
+
+        RETURN QUERY
+            SELECT * FROM search.highlight_display_fields_impl(
+                rid, tsq, afields, css_class, hl_all,minwords,
+                maxwords, shortwords, maxfrags, delimiter
+            );
+    END LOOP;
+
+    RETURN QUERY
+        SELECT  id,
+                source,
+                field,
+                evergreen.escape_for_html(value) AS value,
+                evergreen.escape_for_html(value) AS highlight
+          FROM  metabib.display_entry
+          WHERE source = rid
+                AND NOT (field = ANY (seen));
+END;
+$f$ LANGUAGE PLPGSQL ROWS 10;
+
+COMMIT;
index 9128190..7f0f58e 100755 (executable)
@@ -91,7 +91,7 @@
         '<span property="about"><!-- highlighted -->';
         %]<a href="[%-
                mkurl(ctx.opac_root _ '/results', {qtype=>'subject', query=>total_term}, stop_parms.merge(expert_search_parms, general_search_parms, browse_search_parms, facet_search_parms))
-        -%]">[% s.$f | html %]</a> [%-
+        -%]">[% s.$f %]</a> [%-
         '</span>';
     END;
 %]
index ecdeebf..851e3bd 100755 (executable)
@@ -56,7 +56,7 @@ ctx.metalinks.push('
     <div class="col-lg-9">
         <div class="row h-100">
             <div class="col-lg-8">
-                <h1 class='h1' property="name">[% IF attrs.hl.title; attrs.hl.title | html; ELSE; attrs.title_extended | html; END %]</h1>
+                <h1 class='h1' property="name">[% IF attrs.hl.title; attrs.hl.title; ELSE; attrs.title_extended | html; END %]</h1>
                 [%-
                 FOR link880 IN attrs.graphic_titles;
                 FOR alt IN link880.graphic;
@@ -127,7 +127,7 @@ ctx.metalinks.push('
                             [%- IF attrs.hl.edition %]
                             <li id='rdetail_edition'>
                                 <strong class='rdetail_label'>[% l("Edition:") %]</strong>
-                                <span class='rdetail_value' highlighted='true'>[% attrs.hl.edition | html %]</span>
+                                <span class='rdetail_value' highlighted='true'>[% attrs.hl.edition %]</span>
                                 [%- ELSIF attrs.edition %]
                             <li id='rdetail_edition'>
                                 <strong class='rdetail_label'>[% l("Edition:") %]</strong>
@@ -152,7 +152,7 @@ ctx.metalinks.push('
                             [%- IF attrs.hl.publisher %]
                             <li id='rdetail_publisher'>
                                 <strong class='rdetail_label'>[% l("Publisher:") %]</strong>
-                                <span class='rdetail_value' highlighted='true'>[% attrs.hl.publisher | html %]</span>
+                                <span class='rdetail_value' highlighted='true'>[% attrs.hl.publisher %]</span>
                             </li>
                             [%- ELSIF attrs.publisher %]
                             <li id='rdetail_publisher'>