diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjax.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjax.java index 221ed442593a..77b10133e2d8 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjax.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjax.java @@ -950,14 +950,29 @@ else if( fieldbcontentname.startsWith("date") ){ fieldValueStr = fieldValueStr.replaceAll(specialCharsToEscape, "\\\\$1"); - if(fieldName.equals("languageId") || fieldValueStr.contains("-")){ + if("catchall".equals(fieldName)) { + // Mandatory gate: match either a catchall token PREFIX (fast, existing + // behavior) OR the raw title via wildcard. Unlike catchall (which + // aggregates every field of the document), title_dotraw is scoped to a + // single field, so this alternative recovers mid-token and + // exact-full-value matches -- e.g. a file named "IMG_0004.jpeg" + // tokenizes to "img_0004"+"jpeg", so "0004" or a full-name search never + // satisfies a catchall-only prefix gate -- without reintroducing an + // unscoped, whole-document wildcard like the old broad catchall:*value*. + // Boosts are deliberately asymmetric: catchall (a real token-prefix hit) + // outranks title_dotraw (a raw substring hit that can land anywhere, + // including mid-word). Mirrors GlobalSearchAttributeStrategy, which is + // the equivalent gate for the new Content Search / Content Drive path. + luceneQuery.append("+(catchall:" + fieldValueStr + "*^10 OR title_dotraw:*" + + fieldValueStr + "*^2) "); + } else if(fieldName.equals("languageId") || fieldValueStr.contains("-")){ luceneQuery.append("+" + fieldName +":" + fieldValueStr + " "); }else{ luceneQuery.append("+" + fieldName +":" + fieldValueStr + "* "); } - + if("catchall".equals(fieldName)) { - + luceneQuery.append(" title:'" + fieldValueStr + "'^15 "); final String[] titleSplit = fieldValueStr.split("[,|\\s+]"); if (titleSplit.length > 1) { @@ -965,7 +980,6 @@ else if( fieldbcontentname.startsWith("date") ){ luceneQuery.append(" title:" + term + "^5 "); } } - luceneQuery.append(" title_dotraw:*" + fieldValueStr + "*^5 "); } diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjaxTest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjaxTest.java index 3ff017087712..eedfff67dd8c 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjaxTest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjaxTest.java @@ -17,6 +17,8 @@ import com.dotcms.datagen.ContentTypeDataGen; import com.dotcms.datagen.ContentletDataGen; import com.dotcms.datagen.FieldDataGen; +import com.dotcms.datagen.FileAssetDataGen; +import com.dotcms.datagen.FolderDataGen; import com.dotcms.datagen.LanguageDataGen; import com.dotcms.languagevariable.business.LanguageVariableAPI; import com.dotcms.repackage.org.directwebremoting.WebContext; @@ -33,6 +35,7 @@ import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.contentlet.model.IndexPolicy; import com.dotmarketing.portlets.folders.business.FolderAPI; +import com.dotmarketing.portlets.folders.model.Folder; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.portlets.structure.model.Relationship; import com.dotmarketing.portlets.structure.model.Structure; @@ -42,6 +45,7 @@ import com.google.common.collect.ImmutableList; import com.liferay.portal.model.User; import com.liferay.portal.util.WebKeys; +import com.liferay.util.FileUtil; import com.liferay.util.StringPool; import com.liferay.util.servlet.SessionMessages; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -56,6 +60,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; +import java.io.File; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -535,4 +541,73 @@ public void test_searchContentletsByUser_shouldReturnContentWithMultiLang() thro } + /** + * Method to Test: {@link ContentletAjax#searchContentletsByUser(List, String, List, List, + * boolean, boolean, boolean, boolean, int, String, int, User, HttpSession, String, String)}
+ * When: the legacy Relationships search dialog performs a global ({@code catchall}) + * search for a term that lands mid-token, or that spans a tokenizer boundary such as the exact + * full file name
+ * Should: Find the content anyway, while a term present in no field still returns
+ * nothing — i.e. the broad, unscoped {@code catchall:*value*} is not reintroduced. See issue
+ * #37052.
+ */
+ @Test
+ public void test_searchContentletsByUser_globalSearch_matchesMidTokenAndExactFullName()
+ throws Exception {
+
+ final String uniqueToken = "qa" + System.currentTimeMillis();
+ // Tokenizes to "img_