From d8d4ed8237fe659c078a37b89bdb4a900d11910c Mon Sep 17 00:00:00 2001
From: Kazuhiko Shiozaki <kazuhiko@nexedi.com>
Date: Fri, 21 Nov 2014 16:57:40 +0100
Subject: [PATCH] rewrite SQLCatalog_makeSearchTextQuery without using ad-hoc
 Base_parseSearchString.

---
 .../SQLCatalog_makeSearchTextQuery.xml        |  57 ++------
 .../extension.erp5.SearchUtility.py           |  86 ++++++++++++
 .../extension.erp5.SearchUtility.xml          | 123 ++++++++++++++++++
 .../bt/template_extension_id_list             |   1 +
 4 files changed, 222 insertions(+), 45 deletions(-)
 create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.py
 create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.xml
 create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_extension_id_list

diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/SQLCatalog_makeSearchTextQuery.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/SQLCatalog_makeSearchTextQuery.xml
index ada90d95f5..1d3e12e62c 100644
--- a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/SQLCatalog_makeSearchTextQuery.xml
+++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/SQLCatalog_makeSearchTextQuery.xml
@@ -50,9 +50,7 @@
         </item>
         <item>
             <key> <string>_body</string> </key>
-            <value> <string encoding="cdata"><![CDATA[
-
-"""\n
+            <value> <string>"""\n
   Search text query generator. Accepts a string and returns a ComplexQuery.\n
   For example:\n
 \n
@@ -67,48 +65,17 @@
   - are contributed by given Person\'s title\n
   - etc ..\n
 """\n
-from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery\n
-\n
-parsed_dict = context.Base_parseSearchString(value)\n
-full_text = parsed_dict.pop(\'searchabletext\')\n
-\n
-#context.log(parsed_dict)\n
-\n
-# use full_text scriptable key!\n
-query_list = [Query(**{\'full_text\': full_text})]\n
-\n
-# XXX: "newest" ?\n
-# XXX: get known columns rather than hard-code \n
-for key in (\'reference\', \'version\', \'language\', \'portal_type\', \'contributor_title\'):\n
-  value = parsed_dict.get(key, None)\n
-  if value is not None:\n
-    query_list.append(Query(**{key: value}))\n
-\n
-# user wants only his documents\n
-if parsed_dict.get(\'mine\', None) is not None:\n
-  query_list.append(Query(**{\'owner\': str(context.portal_membership.getAuthenticatedMember())}))\n
-\n
-creation_from = parsed_dict.get(\'creation_from\', None)\n
-creation_to = parsed_dict.get(\'creation_to\', None)\n
-modification_from = parsed_dict.get(\'modification_from\', None)\n
-modification_to = parsed_dict.get(\'modification_to\', None)\n
-\n
-if creation_from is not None:\n
-  query_list.append(SimpleQuery(**{\'catalog.creation_date\': creation_from, \n
-                                   \'comparison_operator\': \'>=\'}))\n
-if creation_to is not None:\n
-  query_list.append(SimpleQuery(**{\'catalog.creation_date\': creation_to, \n
-                                   \'comparison_operator\': \'=<\'}))\n
-if modification_from is not None:\n
-  query_list.append(SimpleQuery(**{\'catalog.modification_date\': modification_from, \n
-                                   \'comparison_operator\': \'>=\'}))\n
-if modification_to is not None:\n
-  query_list.append(SimpleQuery(**{\'catalog.modification_date\': modification_to, \n
-                                   \'comparison_operator\': \'<=\'}))\n
-return ComplexQuery(query_list, operator="AND")\n
-
-
-]]></string> </value>
+if \'full_text\' in context.sql_search_tables:\n
+  column = \'SearchableText\'\n
+else:\n
+  column = \'title\'\n
+node = context.Base_getAdvancedSearchSyntaxTreeNode(value, column=column)\n
+if node is None:\n
+  from Products.ZSQLCatalog.SQLCatalog import Query\n
+  return Query(**{column:value})\n
+else:\n
+  return context.getPortalObject().portal_catalog.getSQLCatalog().buildQueryFromAbstractSyntaxTreeNode(node, column)\n
+</string> </value>
         </item>
         <item>
             <key> <string>_params</string> </key>
diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.py b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.py
new file mode 100644
index 0000000000..49d6d09869
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.py
@@ -0,0 +1,86 @@
+##############################################################################
+#
+# Copyright (c) 2014 Nexedi SA and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from DateTime import DateTime
+from Products.ERP5Type.DateUtils import addToDate
+import re
+
+def render_date_range(date_range):
+  m = re.match(r'(\d+)([dwmy])', date_range)
+  if m is not None:
+    period_dict = {
+      'd':'day',
+      'w':'week',
+      'm':'month',
+      'y':'year',
+    }
+    num, period = m.groups()
+    period = period_dict[period.lower()]
+    return addToDate(DateTime(), **{period:-int(num)})
+
+def recurseUpdateSyntaxNode(self, node):
+  if node.isLeaf():
+    pass
+  elif node.isColumn():
+    if node.column_name == 'mine':
+      node.column_name = 'owner'
+      node.node.value = str(self.portal_membership.getAuthenticatedMember())
+    elif node.column_name == 'state' and node.node.value != 'all':
+      node.column_name = 'simulation_state'
+    elif node.column_name == 'type' and node.node.value != 'all':
+      node.column_name = 'portal_type'
+    elif node.column_name == 'file':
+      node.column_name = 'source_reference'
+    elif node.column_name == 'filetype':
+      node.column_name = 'source_reference'
+      node.node.value = '%%.%s' % node.node.value
+    elif node.column_name == 'created':
+      node.column_name = 'creation_date'
+      node.node.value = render_date_range(node.node.value)
+      node.node.comparison_operator = '>='
+    elif node.column_name == 'creation_from':
+      node.column_name = 'creation_date'
+      node.node.comparison_operator = '>='
+    elif node.column_name == 'creation_to':
+      node.column_name = 'creation_date'
+      node.node.comparison_operator = '<='
+    elif node.column_name == 'modification_from':
+      node.column_name = 'modification_date'
+      node.node.comparison_operator = '>='
+    elif node.column_name == 'modification_to':
+      node.column_name = 'modification_date'
+      node.node.comparison_operator = '<='
+  else:
+    for subnode in node.getNodeList():
+      recurseUpdateSyntaxNode(self, subnode)
+
+def getAdvancedSearchSyntaxTreeNode(self, search_text, column):
+  sql_catalog = self.getPortalObject().portal_catalog.getSQLCatalog()
+  node = sql_catalog.parseSearchText(search_text=search_text, column=column, is_valid=lambda x:True)
+  if node is not None:
+    recurseUpdateSyntaxNode(self, node)
+  return node
diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.xml
new file mode 100644
index 0000000000..09f85216d4
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/ExtensionTemplateItem/portal_components/extension.erp5.SearchUtility.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Extension Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_recorded_property_dict</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>SearchUtility</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>extension.erp5.SearchUtility</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Extension Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary/>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="4" aka="AAAAAAAAAAQ=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
+    </pickle>
+    <pickle>
+      <tuple>
+        <none/>
+        <list>
+          <dictionary>
+            <item>
+                <key> <string>action</string> </key>
+                <value> <string>validate</string> </value>
+            </item>
+            <item>
+                <key> <string>validation_state</string> </key>
+                <value> <string>validated</string> </value>
+            </item>
+          </dictionary>
+        </list>
+      </tuple>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_extension_id_list b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_extension_id_list
new file mode 100644
index 0000000000..fa6ef61e1d
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/bt/template_extension_id_list
@@ -0,0 +1 @@
+extension.erp5.SearchUtility
\ No newline at end of file
-- 
2.30.9