Friday, October 25, 2019

Drupal 7 / Ubercart 7 - Implementing FUZZY Search

Drupal core 7.67 / Ubercart 7.x-3.13 


Implementing FUZZY Search in Drupal 7


Customers don't always remember the exact name of the product they are looking for, so they typically want to have FUZZY search capabilities, which means they enter a partial search term to get a list of candidate answers to then pick from.

A good example of this is a product we have that is very popular, but has a bit of a strange name:  OCU-GLO.  This name has a variant spelling and also a hypen in the middle, which leads to a lot of confusion if you've only heard the name, but not seen it.

Unfortunately, the default search behaviour of Drupal is exact search.  This is really stupid in an age where everyone is using Natural Language Processing (NLP) in their search, including Google - who has offered it for 20 years!

This is what happens on a stock Drupal 7 site when an incomplete term like "ocu" is used to initiate a search:



Stupid, stupid, stupid.

This feature has been requested by Drupal 7 users since 2009, yet it has not made itself into the stock Drupal 7 distribution, even as Drupal 7 is being "sunsetted" by its maintainers, who in my opinion are doing a terrible job supporting their user community.

Rant

Several of the features we need appear to still require CORE modifications, even seven years after the releast of Drupal 7.  Of course, this is very silly in our customer-centered era - but to be expected with such a disorganized setup as the Drupal organization, which features an achingly slow, authoritarian central planning committee and a fanatical but glaringly incomplete engineering and product focus to the point where its output is actually detrimental to the customer organization (i.e. putting Drupal in and getting it working wastes a lot of time).  So, ordinary users are forced to hack the source code of Drupal, even as Drupal high priests ominously intone that "hacking Drupal Core is forbidden behaviour" - while taking years to fix critical things!  Of course, this is incredibly frustrating for someone who has a business to run on Drupal, like us, mostly because we stupidly chose Drupal in 2010 and are now desperately trying to avoid the massive migration costs of moving across eCommerce platforms, even as they are forced to re-implement their entire back-end within the Drupal eCommerce platform because its upgrade model is basically broken.  Drupal really is the gift that keeps on taking.

How to Patch Drupal 

1.  Document the Patch

Extensively document the patch because you may need to re-implement it if you upgrade Drupal Core, and things will stop working after an upgrade so in the heat of your WTF you will want to go somewhere you wrote stuff with a cool head, like here on Blogger(tm), thus helping along others who are also struggling to make Drupal what it could be, rather than what it is.

2.  Determine Where to Apply the Patch


You can figure out where the patch is to be applied by looking at the header of the patch file.  Here's the location of the patch file we are going to use as an example:

https://www.drupal.org/files/issues/search-partial_words_hack-498752-41_0.patch

Here's the entire source code of the above file, just in case it disappears one day:


diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc
index 4074256..827ca3a 100644
--- a/modules/search/search.extender.inc
+++ b/modules/search/search.extender.inc
@@ -285,7 +285,7 @@ class SearchQuery extends SelectQueryExtender {
         foreach ($key as $or) {
           list($num_new_scores) = $this->parseWord($or);
           $any |= $num_new_scores;
-          $queryor->condition('d.data', "% $or %", 'LIKE');
+          $queryor->condition('d.data', "%$or%", 'LIKE');
         }
         if (count($queryor)) {
           $this->conditions->condition($queryor);
@@ -297,7 +297,7 @@ class SearchQuery extends SelectQueryExtender {
       else {
         $simple_and = TRUE;
         list($num_new_scores, $num_valid_words) = $this->parseWord($key);
-        $this->conditions->condition('d.data', "% $key %", 'LIKE');
+        $this->conditions->condition('d.data', "%$key%", 'LIKE');
         if (!$num_valid_words) {
           $this->simple = FALSE;
         }
@@ -310,7 +310,7 @@ class SearchQuery extends SelectQueryExtender {
     }
     // Negative matches.
     foreach ($this->keys['negative'] as $key) {
-      $this->conditions->condition('d.data', "% $key %", 'NOT LIKE');
+      $this->conditions->condition('d.data', "%$key%", 'NOT LIKE');
       $this->simple = FALSE;
     }
 
@@ -366,7 +366,7 @@ class SearchQuery extends SelectQueryExtender {
     if (!empty($this->words)) {
       $or = db_or();
       foreach ($this->words as $word) {
-        $or->condition('i.word', $word);
+        $or->condition('i.word', '%' . $word . '%', 'LIKE');
       }
       $this->condition($or);
     }

Examine the header of the patch file, which shows the location of the file to patch:


diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc
index 4074256..827ca3a 100644
--- a/modules/search/search.extender.inc
+++ b/modules/search/search.extender.inc

OK, the file in question is search.extender.inc, which lives in the /modules/search/ directory


3.  Verify Where to Put the Patch

Don't blindly believe the patch file paths.  Things change and, remember, there are TWO /modules directories in Drupal (yes, very stupid), so verifying where to put the patch file is necessary.

Figuring out where exactly to put the patch is pretty easy using the find command:

# pwd
/var/www/vhosts/holisticpethelp.com/www/mobile
# find . -name "search.extender.inc"
./modules/search/search.extender.inc

4.  Go to Where the Target File Is

Next, physically go to where the file to be patched is:

# cd modules/search/
# ls
search.admin.inc           search.extender.inc  search.pages.inc        search.test
search.api.php             search.info          search-results.tpl.php  tests
search-block-form.tpl.php  search.install       search-result.tpl.php
search.css                 search.module        search-rtl.css



5.  Back up the Target File



Create a copy of the file to be changed, because who knows if the patch will be successful, or even solve your problem - Remember, you are in "wildcatting" programmer mode, with no guarantee of help from the incredibly slow formal Drupal team



# cp search.extender.inc search.extender.inc.backup-2019-11-25
# ls
search.admin.inc                      search.module
search.api.php                        search.pages.inc
search-block-form.tpl.php             search-partial_words_hack-498752-41_0.patch
search.css                            search-results.tpl.php
search.extender.inc                   search-result.tpl.php
search.extender.inc.backup-2019-11-25  search-rtl.css
search.info                           search.test
search.install                        tests


6.  Install the Patch


You can put the patch in the same directory as the module to be patched using wget:



wget https://www.drupal.org/files/issues/search-partial_words_hack-498752-41_0.patch
--2019-10-25 10:05:31--  https://www.drupal.org/files/issues/search-partial_words_hack-498752-41_0.patch
Resolving www.drupal.org (www.drupal.org)... 151.101.10.217
Connecting to www.drupal.org (www.drupal.org)|151.101.10.217|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1650 (1.6K) [text/plain]
Saving to: ‘search-partial_words_hack-498752-41_0.patch’

100%[====================================================>] 1,650       --.-K/s   in 0s

2019-10-25 10:05:32 (182 MB/s) - ‘search-partial_words_hack-498752-41_0.patch’ saved [1650/1650]

7.  Verify Patch Installation

# ls
search.admin.inc                       search.module
search.api.php                         search.pages.inc
search-block-form.tpl.php              search-partial_words_hack-498752-41_0.patch
search.css                             search-results.tpl.php
search.extender.inc                    search-result.tpl.php
search.extender.inc.backup-2019-11-25  search-rtl.css
search.info                            search.test
search.install                         tests

8.  Apply the Patch (In Verbose Mode)

You can apply the patch using the patch command as described above, which will below, replacing hello.patch with the name of your particular patch file.  For those who are a bit paranoid and sceptical when it comes to Drupal (me), you might want to verify that the changes were actually made in code using the --verbose option, which tells you what patch is up to as it does its thing:

# patch --verbose < search-partial_words_hack-498752-41_0.patch
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc
|index 4074256..827ca3a 100644
|--- a/modules/search/search.extender.inc
|+++ b/modules/search/search.extender.inc
--------------------------
patching file search.extender.inc
Using Plan A...
Hunk #1 succeeded at 285.
Hunk #2 succeeded at 297.
Hunk #3 succeeded at 310.
Hunk #4 succeeded at 366.
done

9.  Validate the Patch Was Applied

Considering that 

  • There were four changes detailed in the above diff file (+/-)
  • Four Hunks were successful in the patch
I'd say that this patch application was a success.

10.  Validate Desired Website Behaviour



REFERENCES:

https://www.thegeekstuff.com/2014/12/patch-command-examples/


https://www.drupal.org/project/drupal/issues/498752

No comments:

Post a Comment