diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java index 925d76364f..629cd06883 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java @@ -137,6 +137,7 @@ public class DisplayVocabulary { /* URI of property for Fixed HTML Generator */ public static final String FIXED_HTML_VALUE = DISPLAY_NS + "htmlValue"; + public static final String SEARCH_FILTER_VALUE = DISPLAY_NS + "searchFilter"; /* URI of property for Search Query Generator */ diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java index f5e38bbc60..660ffed8fa 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java @@ -12,6 +12,7 @@ public class VitroVocabulary { public static final String VITRO_AUTH = "http://vitro.mannlib.cornell.edu/ns/vitro/authorization#"; public static final String VITRO_PUBLIC = "http://vitro.mannlib.cornell.edu/ns/vitro/public#"; public static final String VITRO_PUBLIC_ONTOLOGY = "http://vitro.mannlib.cornell.edu/ns/vitro/public"; + public static final String VITRO_SEARCH_INDIVIDUAL ="https://vivoweb.org/ontology/vitro-search-individual/"; // TODO change the following before 1.6 release public static final String PROPERTY_CONFIG_DATA = "http://vitro.mannlib.cornell.edu/ns/vitro/siteConfig/"; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManagePageGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManagePageGenerator.java index 9f259afa10..818defefc2 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManagePageGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManagePageGenerator.java @@ -526,6 +526,8 @@ private void addRequiredPageData(VitroRequest vreq, Map data) { MenuManagementDataUtils.includeRequiredSystemData(vreq.getSession().getServletContext(), data); data.put("classGroup", new ArrayList()); data.put("classGroups", DataGetterUtils.getClassGroups(vreq)); + data.put("searchFilter", new ArrayList()); + data.put("searchFilters", DataGetterUtils.getSearchFilters(vreq)); //for search individuals data get getter data.put("classes", this.getAllVClasses(vreq)); data.put("availablePermissions", this.getAvailablePermissions(vreq)); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Map.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Map.java index b307eb0d8d..a919b27747 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Map.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Map.java @@ -7,29 +7,37 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.ClassGroupPageData; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.FixedHTMLDataGetter; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.IndividualsForClassesDataGetter; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SearchFilterValuesDataGetter; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SearchIndividualsDataGetter; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SparqlQueryDataGetter; + /* * This class determines what n3 should be returned for a particular data getter and can be overwritten or extended in VIVO. */ public class ProcessDataGetterN3Map { private static final Log log = LogFactory.getLog(ProcessDataGetterN3Map.class); - private static HashMap dataGetterMap; + private static HashMap dataGetterMap; static { - dataGetterMap = new HashMap(); - dataGetterMap.put("edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SparqlQueryDataGetter", "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils.ProcessSparqlDataGetterN3"); - dataGetterMap.put("edu.cornell.mannlib.vitro.webapp.utils.dataGetter.ClassGroupPageData", "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils.ProcessClassGroupDataGetterN3"); - dataGetterMap.put("edu.cornell.mannlib.vitro.webapp.utils.dataGetter.IndividualsForClassesDataGetter", "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils.ProcessIndividualsForClassesDataGetterN3"); - dataGetterMap.put("edu.cornell.mannlib.vitro.webapp.utils.dataGetter.FixedHTMLDataGetter", "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils.ProcessFixedHTMLN3"); - dataGetterMap.put("edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SearchIndividualsDataGetter", "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils.ProcessSearchIndividualsDataGetterN3"); + dataGetterMap = new HashMap(); + dataGetterMap.put(SparqlQueryDataGetter.class.getCanonicalName(), ProcessSparqlDataGetterN3.class); + dataGetterMap.put(ClassGroupPageData.class.getCanonicalName(), ProcessClassGroupDataGetterN3.class); + dataGetterMap.put(SearchFilterValuesDataGetter.class.getCanonicalName(), ProcessSearchFilterValuesDataGetterN3.class); + dataGetterMap.put(IndividualsForClassesDataGetter.class.getCanonicalName(), ProcessIndividualsForClassesDataGetterN3.class); + dataGetterMap.put(FixedHTMLDataGetter.class.getCanonicalName(), ProcessFixedHTMLN3.class); + dataGetterMap.put(SearchIndividualsDataGetter.class.getCanonicalName(), ProcessSearchIndividualsDataGetterN3.class); } - public static HashMap getDataGetterTypeToProcessorMap() { + public static HashMap getDataGetterTypeToProcessorMap() { return dataGetterMap; } - public static void replaceDataGetterMap(HashMap newMap) { - dataGetterMap = new HashMap(); + public static void replaceDataGetterMap(HashMap newMap) { + dataGetterMap = new HashMap(); dataGetterMap.putAll(newMap); } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Utils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Utils.java index 0a40c447c3..aa436778ce 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Utils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessDataGetterN3Utils.java @@ -17,26 +17,28 @@ public class ProcessDataGetterN3Utils { private static final Log log = LogFactory.getLog(ProcessDataGetterN3Utils.class); public static ProcessDataGetterN3 getDataGetterProcessorN3(String dataGetterClass, ObjectNode jsonObject) { - HashMap map = ProcessDataGetterN3Map.getDataGetterTypeToProcessorMap(); + HashMap map = ProcessDataGetterN3Map.getDataGetterTypeToProcessorMap(); // if(map.containsKey(dataGetterClass)) { - String processorClass = map.get(dataGetterClass); + Class processorClass = map.get(dataGetterClass); try { ProcessDataGetterN3 pn = instantiateClass(processorClass, jsonObject); return pn; } catch(Exception ex) { log.error("Exception occurred in trying to get processor class for n3 for " + dataGetterClass, ex); return null; - } + } + } else { + log.error(ProcessDataGetterN3Map.class.getSimpleName() + " doesn't contain processor class for n3 for " + dataGetterClass); + return null; } - return null; + } - private static ProcessDataGetterN3 instantiateClass(String processorClass, ObjectNode jsonObject) { + private static ProcessDataGetterN3 instantiateClass(Class processorClass, ObjectNode jsonObject) { ProcessDataGetterN3 pn = null; try { - Class clz = Class.forName(processorClass); - Constructor[] ctList = clz.getConstructors(); + Constructor[] ctList = processorClass.getConstructors(); for (Constructor ct: ctList) { Class[] parameterTypes =ct.getParameterTypes(); if(parameterTypes.length > 0 && parameterTypes[0].isAssignableFrom(jsonObject.getClass())) { @@ -47,7 +49,7 @@ private static ProcessDataGetterN3 instantiateClass(String processorClass, Objec } } catch(Exception ex) { - log.error("Error occurred instantiating " + processorClass, ex); + log.error("Error occurred instantiating " + processorClass.getCanonicalName(), ex); } return pn; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessSearchFilterValuesDataGetterN3.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessSearchFilterValuesDataGetterN3.java new file mode 100644 index 0000000000..7576ff1809 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/utils/ProcessSearchFilterValuesDataGetterN3.java @@ -0,0 +1,157 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ + +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.utils; + +import static edu.cornell.mannlib.vitro.webapp.dao.DisplayVocabulary.SEARCH_FILTER_VALUE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.ServletContext; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.cornell.mannlib.vitro.webapp.dao.DisplayVocabulary; +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SearchFilterValuesDataGetter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.ontology.OntModel; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.Resource; + +//Returns the appropriate n3 based on data getter +public class ProcessSearchFilterValuesDataGetterN3 extends ProcessDataGetterAbstract { + private static String classType = "java:" + SearchFilterValuesDataGetter.class.getCanonicalName(); + public static String searchFilterVarBase = "filterUri"; + private Log log = LogFactory.getLog(ProcessSearchFilterValuesDataGetterN3.class); + + public ProcessSearchFilterValuesDataGetterN3() { + } + + public List retrieveN3Required(int counter) { + return retrieveN3ForTypeAndFilter(counter); + } + + public List retrieveN3Optional(int counter) { + return null; + } + + public List retrieveN3ForTypeAndFilter(int counter) { + String n3ForType = getN3ForTypePartial(counter); + String n3 = n3ForType + "; \n" + "<" + DisplayVocabulary.SEARCH_FILTER_VALUE + "> " + + getN3VarName(searchFilterVarBase, counter) + " ."; + List n3List = new ArrayList(); + n3List.add(getPrefixes() + n3); + return n3List; + } + + public String getN3ForTypePartial(int counter) { + String dataGetterVar = getDataGetterVar(counter); + String classTypeVar = getN3VarName(classTypeVarBase, counter); + String n3 = dataGetterVar + " a " + classTypeVar; + return n3; + } + + public List retrieveLiteralsOnForm(int counter) { + // no literals, just the class group URI + List literalsOnForm = new ArrayList(); + return literalsOnForm; + } + + public List retrieveUrisOnForm(int counter) { + List urisOnForm = new ArrayList(); + urisOnForm.add(getVarName(searchFilterVarBase, counter)); + urisOnForm.add(getVarName(classTypeVarBase, counter)); + return urisOnForm; + } + + public List retrieveFields(int counter) { + List fields = new ArrayList(); + fields.add(new FieldVTwo().setName(getVarName(searchFilterVarBase, counter))); + fields.add(new FieldVTwo().setName(getVarName(classTypeVarBase, counter))); + return fields; + } + + public List getLiteralVarNamesBase() { + return Arrays.asList(); + } + + public List getUriVarNamesBase() { + return Arrays.asList(searchFilterVarBase, classTypeVarBase); + } + + public String getClassType() { + return classType; + } + + public void populateExistingValues(String dataGetterURI, int counter, OntModel queryModel) { + // First, put dataGetterURI within scope as well + this.populateExistingDataGetterURI(dataGetterURI, counter); + // Put in type + this.populateExistingClassType(this.getClassType(), counter); + // Sparql queries for values to be executed + // And then placed in the correct place/literal or uri + String querystr = getExistingValuesClassGroup(dataGetterURI); + QueryExecution qe = null; + try { + Query query = QueryFactory.create(querystr); + qe = QueryExecutionFactory.create(query, queryModel); + ResultSet results = qe.execSelect(); + while (results.hasNext()) { + QuerySolution qs = results.nextSolution(); + Resource classGroupResource = qs.getResource(searchFilterVarBase); + // Put both literals in existing literals + existingUriValues.put(this.getVarName(searchFilterVarBase, counter), + new ArrayList(Arrays.asList(classGroupResource.getURI()))); + } + } catch (Exception ex) { + log.error("Exception occurred in retrieving existing values with query " + querystr, ex); + } + } + + protected String getExistingValuesClassGroup(String dataGetterURI) { + String query = getSparqlPrefix() + "\n" + + "SELECT ?filterUri ?filterName WHERE {" + + "<" + dataGetterURI + "> <" + SEARCH_FILTER_VALUE + "> ?filterUri .\n" + + "?filterUri <" + VitroVocabulary.LABEL + "> ?filterName .\n" + + "}"; + return query; + } + + public ObjectNode getExistingValuesJSON(String dataGetterURI, OntModel queryModel, ServletContext context) { + ObjectNode jObject = new ObjectMapper().createObjectNode(); + jObject.put("dataGetterClass", classType); + jObject.put(classTypeVarBase, classType); + getExistingSearchFilters(dataGetterURI, jObject, queryModel); + return jObject; + } + + private void getExistingSearchFilters(String dataGetterURI, ObjectNode jObject, OntModel queryModel) { + String querystr = getExistingValuesClassGroup(dataGetterURI); + QueryExecution qe = null; + try { + Query query = QueryFactory.create(querystr); + qe = QueryExecutionFactory.create(query, queryModel); + ResultSet results = qe.execSelect(); + while (results.hasNext()) { + QuerySolution qs = results.nextSolution(); + Resource filterUri = qs.getResource(searchFilterVarBase); + Literal name = qs.getLiteral("filterName"); + jObject.put("searchFilterUri", filterUri.getURI()); + jObject.put("searchFilterName", name.getLexicalForm()); + } + } catch (Exception ex) { + log.error("Exception occurred in retrieving existing values with query " + querystr, ex); + } + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/searchEngine/SearchQuery.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/searchEngine/SearchQuery.java index 15c687330d..9ea30e1b97 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/searchEngine/SearchQuery.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/searchEngine/SearchQuery.java @@ -139,4 +139,26 @@ public enum Order { * @return A negative value means that no limit has been specified. */ int getFacetMinCount(); + + /** + * @param text is the text a facet field should contain. + * @return this query + */ + SearchQuery setFacetTextToMatch(String text); + + /** + * @return The text a facet field. + */ + String getFacetTextToMatch(); + + /** + * @param ignoreCase defines whether the text of a facet field should be compared case insensitively. + * @return this query + */ + SearchQuery setFacetTextCompareCaseInsensitive(boolean ignoreCase); + + /** + * @return defines whether the text of a facet field should be compared case insensitively. + */ + boolean isFacetTextCompareCaseInsensitive(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/FilterValue.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/FilterValue.java index 20ce51fc00..0ec4c9b9f1 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/FilterValue.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/FilterValue.java @@ -6,26 +6,18 @@ public class FilterValue { private String id; private String name = ""; - private int order; - + private int rank; private long count; - private boolean selected = false; - private boolean isDefaultValue; + private boolean displayed = false; - private boolean publiclyAvailable = true; - - public boolean isPubliclyAvailable() { - return publiclyAvailable; + public boolean isDisplayed() { + return displayed; } - public void setPubliclyAvailable(RDFNode rdfNode) { - if (rdfNode != null && rdfNode.isLiteral()) { - publiclyAvailable = rdfNode.asLiteral().getBoolean(); - } else { - publiclyAvailable = false; - } + public void setDisplayed(boolean value) { + displayed = value; } public FilterValue(String id) { @@ -50,13 +42,13 @@ public void setName(RDFNode rdfNode) { } } - public Integer getOrder() { - return order; + public Integer getRank() { + return rank; } - public void setOrder(RDFNode rdfNode) { + public void setRank(RDFNode rdfNode) { if (rdfNode != null) { - order = rdfNode.asLiteral().getInt(); + rank = rdfNode.asLiteral().getInt(); } } @@ -76,7 +68,7 @@ public void setSelected(boolean value) { this.selected = value; } - public boolean getSelected() { + public boolean isSelected() { return selected; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java index ce4ff9a277..63a652f89f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java @@ -7,7 +7,6 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; -import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -15,6 +14,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -78,7 +78,7 @@ public class PagedSearchController extends FreemarkerHttpServlet { public static final String PARAM_QUERY_TEXT = "querytext"; public static final String PARAM_QUERY_SORT_BY = "sort"; - protected static final Map> templateTable; + protected static final Map> templateTable = setupTemplateTable(); protected enum Format { HTML, @@ -92,10 +92,6 @@ protected enum Result { BAD_QUERY } - static { - templateTable = setupTemplateTable(); - } - /** * Overriding doGet from FreemarkerHttpController to do a page template (as opposed to body template) style output * for XML requests. @@ -142,7 +138,11 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro @Override protected ResponseValues processRequest(VitroRequest vreq) { + Map> requestFilters = SearchFiltering.getRequestFilters(vreq); + return process(vreq, requestFilters); + } + public static ResponseValues process(VitroRequest vreq, Map> requestFilters) { // There may be other non-html formats in the future Format format = getFormat(vreq); boolean wasXmlRequested = Format.XML == format; @@ -180,28 +180,22 @@ protected ResponseValues processRequest(VitroRequest vreq) { log.debug(getSpentTime(startTime) + "ms spent before read filter configurations."); } Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq); - Map filterConfigurationsByField = SearchFiltering.readFilterConfigurations(currentRoles, vreq); + Map filters = SearchFiltering.readFilterConfigurations(currentRoles, vreq); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent before get sort configurations."); } - for (SearchFilter filter: filterConfigurationsByField.values()) { - filter.setInputText(SearchFiltering.getFilterInputText(vreq, filter.getId())); - filter.setRangeValues(SearchFiltering.getFilterRangeText(vreq, filter.getId())); - } - Map> requestFilters = SearchFiltering.getRequestFilters(vreq); - if (log.isDebugEnabled()) { - log.debug(getSpentTime(startTime) + "ms spent after getRequestFilters."); - } - SearchFiltering.setSelectedFilters(filterConfigurationsByField, requestFilters); + + SearchFiltering.setSelectedFilters(filters, requestFilters); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after setSelectedFilters."); } + Map sortConfigurations = SearchFiltering.getSortConfigurations(vreq); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent before get query configurations."); } SearchQuery query = - getQuery(queryText, documentsToReturn, startIndex, vreq, filterConfigurationsByField, sortConfigurations); + getQuery(queryText, documentsToReturn, startIndex, vreq, filters, sortConfigurations); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after get query configurations."); } @@ -223,10 +217,11 @@ protected ResponseValues processRequest(VitroRequest vreq) { log.error("Search response was null"); return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq); } - addFacetCountersFromRequest(response, filterConfigurationsByField, vreq); + addFacetCountersFromRequest(response, filters, vreq); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after addFacetCountersFromRequest."); } + addFilterValueLabels(filters, vreq); SearchResultDocumentList docs = response.getResults(); if (docs == null) { log.error("Document list for a search was null"); @@ -250,12 +245,12 @@ protected ResponseValues processRequest(VitroRequest vreq) { } } - ParamMap pagingLinkParams = new ParamMap(); - pagingLinkParams.put(PARAM_QUERY_TEXT, queryText); - pagingLinkParams.put(PARAM_HITS_PER_PAGE, String.valueOf(hitsPerPage)); + ParamMap searchParams = new ParamMap(); + searchParams.put(PARAM_QUERY_TEXT, queryText); + SearchFiltering.addFiltersToPageLinks(vreq, searchParams, vreq.getParameterNames()); - Enumeration paramNames = vreq.getParameterNames(); - SearchFiltering.addFiltersToPageLinks(vreq, pagingLinkParams, paramNames); + ParamMap pagingLinkParams = new ParamMap(searchParams); + pagingLinkParams.put(PARAM_HITS_PER_PAGE, String.valueOf(hitsPerPage)); if (wasXmlRequested) { pagingLinkParams.put(PARAM_XML_REQUEST, "1"); @@ -270,18 +265,16 @@ protected ResponseValues processRequest(VitroRequest vreq) { if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent before sorting filterConfigurationsByField values."); } - for (Entry entry : filterConfigurationsByField.entrySet()) { - entry.getValue().sortValues(); + for (Entry entry : filters.entrySet()) { + entry.getValue().sortValues(vreq.getCollator()); } if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after sorting filterConfigurationsByField values."); } - Map filtersForTemplateById = - SearchFiltering.getFiltersForTemplate(filterConfigurationsByField); - body.put("filters", filtersForTemplateById); - body.put("filterGroups", SearchFiltering.readFilterGroupsConfigurations(vreq, filtersForTemplateById)); - body.put("sorting", sortConfigurations.values()); - body.put("emptySearch", isEmptySearchFilters(filterConfigurationsByField)); + body.put("filters", filters); + body.put("filterGroups", SearchFiltering.readFilterGroupsConfigurations(vreq, filters)); + body.put("sortOptions", sortConfigurations); + body.put("emptySearch", isEmptySearchFilters(filters)); } body.put("individuals", IndividualSearchResult.getIndividualTemplateModels(individuals, vreq)); @@ -308,6 +301,7 @@ protected ResponseValues processRequest(VitroRequest vreq) { if (startIndex < (hitCount - hitsPerPage)) { body.put("nextPage", getNextPageLink(startIndex, hitsPerPage, vreq.getServletPath(), pagingLinkParams)); } + body.put("facetOptionsUrl", getFacetControllerLink(searchParams, requestFilters, vreq)); String template = templateTable.get(format).get(Result.PAGED); if (log.isDebugEnabled()) { @@ -323,11 +317,27 @@ protected ResponseValues processRequest(VitroRequest vreq) { } } - private long getSpentTime(long startTime) { + + private static void addFilterValueLabels(Map filters, VitroRequest vreq) { + for (SearchFilter filter : filters.values()) { + if (filter.isLocalizationRequired()) { + for (FilterValue value : filter.getValues().values()) { + if (StringUtils.isBlank(value.getName()) && !value.getId().contains(" ")) { + String label = SearchFiltering.getUriLabel(value.getId(), vreq); + if (!StringUtils.isBlank(label)) { + value.setName(label); + } + } + } + } + } + } + + private static long getSpentTime(long startTime) { return (System.nanoTime() - startTime) / 1000000; } - private Object isEmptySearchFilters(Map filterConfigurationsByField) { + private static Object isEmptySearchFilters(Map filterConfigurationsByField) { for (SearchFilter filter : filterConfigurationsByField.values()) { if (filter.isSelected()) { return false; @@ -336,10 +346,11 @@ private Object isEmptySearchFilters(Map filterConfiguratio return true; } - private void addFacetCountersFromRequest(SearchResponse response, Map filtersByField, + private static void addFacetCountersFromRequest(SearchResponse response, Map filters, VitroRequest vreq) { long startTime = System.nanoTime(); - List resultfacetFields = response.getFacetFields(); + Map fields = response.getFacetFields().stream() + .collect(Collectors.toMap(SearchFacetField::getName, Function.identity())); if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after getFacetFields."); } @@ -347,13 +358,12 @@ private void addFacetCountersFromRequest(SearchResponse response, Map values = resultField.getValues(); - for (Count value : values) { if (value.getCount() == 0) { continue; @@ -362,6 +372,7 @@ private void addFacetCountersFromRequest(SearchResponse response, Map>> highlights = response.getHighlighting(); @@ -449,8 +454,8 @@ private String getSnippet(SearchResultDocument doc, SearchResponse response) { return text.toString(); } - private SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, VitroRequest vreq, - Map filtersByField, Map sortOptions) { + public static SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, VitroRequest vreq, + Map filters, Map sortOptions) { // Lowercase the search term to support wildcard searches: The search engine // applies no text // processing to a wildcard search term. @@ -465,24 +470,22 @@ private SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, addDefaultVitroFacets(vreq, query); - SearchFiltering.addFacetFieldsToQuery(filtersByField, query); - - Map filtersById = SearchFiltering.getFiltersById(filtersByField); + SearchFiltering.addFacetFieldsToQuery(filters, query); - SearchFiltering.addFiltersToQuery(vreq, query, filtersById); + SearchFiltering.addFiltersToQuery(query, filters); log.debug("Query = " + query.toString()); return query; } - private void addDefaultVitroFacets(VitroRequest vreq, SearchQuery query) { + private static void addDefaultVitroFacets(VitroRequest vreq, SearchQuery query) { String[] facets = vreq.getParameterValues(FACETS); if (facets != null && facets.length > 0) { query.addFacetFields(facets); } } - private void addSortRules(VitroRequest vreq, SearchQuery query, Map sortOptions) { + private static void addSortRules(VitroRequest vreq, SearchQuery query, Map sortOptions) { String sortType = getSortType(vreq); if (sortOptions.isEmpty()) { return; @@ -503,7 +506,7 @@ private void addSortRules(VitroRequest vreq, SearchQuery query, Map sortOptions, Set appliedSortOptions) { if (conf == null || appliedSortOptions.contains(conf.getId())) { return; @@ -514,13 +517,13 @@ private void addSortField(VitroRequest vreq, SearchQuery query, SortConfiguratio log.error(String.format("Sort field is not set for '%s'", conf.getId())); return; } - query.addSortField(field, conf.getSortOrder()); + query.addSortField(field, conf.getSortDirection()); if (sortOptions.containsKey(conf.getFallback())) { addSortField(vreq, query, sortOptions.get(conf.getFallback()), sortOptions, appliedSortOptions); } } - private String getSortType(VitroRequest vreq) { + private static String getSortType(VitroRequest vreq) { return vreq.getParameter(PARAM_QUERY_SORT_BY); } @@ -558,16 +561,31 @@ protected static List getPagingLinks(int startIndex, int hitsPerPage return pagingLinks; } - private String getPreviousPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { + private static String getPreviousPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { params.put(PARAM_START_INDEX, String.valueOf(startIndex - hitsPerPage)); return UrlBuilder.getUrl(baseUrl, params); } - private String getNextPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { + private static String getNextPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { params.put(PARAM_START_INDEX, String.valueOf(startIndex + hitsPerPage)); return UrlBuilder.getUrl(baseUrl, params); } + private static String getFacetControllerLink(ParamMap params, Map> providedFilters, VitroRequest vreq) { + Map> requestFilters = SearchFiltering.getRequestFilters(vreq); + Set impliedFilters = new HashSet(providedFilters.keySet()); + impliedFilters.removeAll(requestFilters.keySet()); + int i = 0; + for (String filter : impliedFilters) { + List values = providedFilters.get(filter); + for (String value : values) { + params.put(SearchFiltering.FILTERS + "_" + i , filter + ":" + value); + i++; + } + } + return UrlBuilder.getUrl(SearchFacetsController.SEARCH_FACETS_URL, params); + } + protected static class PagingLink extends LinkTemplateModel { PagingLink(int pageNumber, String baseUrl, ParamMap params) { @@ -585,13 +603,13 @@ protected static class PagingLink extends LinkTemplateModel { } } - private ExceptionResponseValues doSearchError(Throwable e, Format f) { + private static ExceptionResponseValues doSearchError(Throwable e, Format f) { Map body = new HashMap(); body.put("message", "Search failed: " + e.getMessage()); return new ExceptionResponseValues(getTemplate(f, Result.ERROR), body, e); } - private TemplateResponseValues doFailedSearch(String message, String querytext, Format f, VitroRequest vreq) { + private static TemplateResponseValues doFailedSearch(String message, String querytext, Format f, VitroRequest vreq) { Map body = new HashMap(); body.put("title", I18n.text(vreq, "search_for", querytext)); if (StringUtils.isEmpty(message)) { @@ -604,7 +622,7 @@ private TemplateResponseValues doFailedSearch(String message, String querytext, /** * Makes a message to display to user for a bad search term. */ - private String makeBadSearchMessage(String querytext, String exceptionMsg, VitroRequest vreq) { + private static String makeBadSearchMessage(String querytext, String exceptionMsg, VitroRequest vreq) { String rv = ""; try { // try to get the column in the search term that is causing the problems @@ -666,7 +684,7 @@ protected boolean isRequestedFormatCSV(VitroRequest req) { } } - protected Format getFormat(VitroRequest req) { + protected static Format getFormat(VitroRequest req) { if (req != null && req.getParameter("xml") != null && "1".equals(req.getParameter("xml"))) { return Format.XML; } else if (req != null && req.getParameter("csv") != null && "1".equals(req.getParameter("csv"))) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFacetsController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFacetsController.java new file mode 100644 index 0000000000..66f9a65b3c --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFacetsController.java @@ -0,0 +1,122 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ + +package edu.cornell.mannlib.vitro.webapp.search.controller; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.ajax.VitroAjaxController; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField.Count; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * SearchFacetsController generates provides autocomplete options from the + * search index. + */ + +@WebServlet(name = "SearchFacetsController", urlPatterns = { SearchFacetsController.SEARCH_FACETS_URL }) +public class SearchFacetsController extends VitroAjaxController { + + public static final String SEARCH_FACETS_URL = "/searchfacets"; + private static final String APPLICATION_JSON = "application/json"; + private static final long serialVersionUID = 1L; + private static final Log log = LogFactory.getLog(SearchFacetsController.class); + private static final String PARAM_TERM = "term"; + private static final String PARAM_FILTER = "facet_filter"; + private static final int MAX_QUERY_LENGTH = 50; + private static ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void doRequest(VitroRequest vreq, HttpServletResponse response) throws IOException, ServletException { + + try { + String term = vreq.getParameter(PARAM_TERM); + String filterParam = vreq.getParameter(PARAM_FILTER); + String field = null; + Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq); + Map filters = SearchFiltering.readFilterConfigurations(currentRoles, vreq); + if (StringUtils.isNotBlank(filterParam)) { + SearchFilter filter = filters.get(filterParam); + if (filter != null && filter.isDisplayed()) { + field = filter.getField(); + } + } + if (StringUtils.isBlank(field)) { + log.debug("field '" + field + "' is empty."); + setEmptySearchResults(response); + return; + } + if (term == null) { + log.error("There was no parameter '" + PARAM_TERM + "' in the request."); + setEmptySearchResults(response); + return; + } else if (term.length() > MAX_QUERY_LENGTH) { + log.debug("The search was too long. The maximum " + "query length is " + MAX_QUERY_LENGTH); + setEmptySearchResults(response); + return; + } + Map> requestFilters = SearchFiltering.getRequestFilters(vreq); + SearchFiltering.setSelectedFilters(filters, requestFilters); + String queryText = PagedSearchController.getQueryText(vreq); + Map sortConfigurations = SearchFiltering.getSortConfigurations(vreq); + SearchQuery query = PagedSearchController.getQuery(queryText, 0, 0, vreq, filters, sortConfigurations); + log.debug("query for '" + term + "' is " + query.toString()); + + query.addFacetFields(field); + query.setFacetMinCount(1); + query.setFacetTextToMatch(term); + query.setFacetTextCompareCaseInsensitive(true); + + SearchEngine search = ApplicationUtils.instance().getSearchEngine(); + SearchResponse queryResponse = search.query(query); + + if (queryResponse == null) { + log.error("Query response for a search was null"); + setEmptySearchResults(response); + return; + } + + SearchFacetField facetField = queryResponse.getFacetField(field); + List values = facetField.getValues(); + + if (values == null) { + log.error("Facet values for a search was null"); + setEmptySearchResults(response); + return; + } + + ArrayNode results = objectMapper.createArrayNode(); + for (Count doc : values) { + String name = doc.getName(); + results.add(name); + } + response.setContentType(APPLICATION_JSON); + response.getWriter().write(results.toString()); + } catch (Throwable e) { + log.error(e, e); + setEmptySearchResults(response); + } + } + + private void setEmptySearchResults(HttpServletResponse response) throws IOException { + response.setContentType(APPLICATION_JSON); + response.getWriter().write("[]"); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java index 8066c6424e..be8753407e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java @@ -1,5 +1,8 @@ package edu.cornell.mannlib.vitro.webapp.search.controller; +import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.VITRO_SEARCH_INDIVIDUAL; + +import java.text.Collator; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -8,17 +11,32 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.stream.Collectors; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery.Order; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.jena.rdf.model.RDFNode; public class SearchFilter { + private static final String DEFAULT_PATTERN = "$0"; private static final String FILTER = "Filter"; private static final String RANGE_FILTER = "RangeFilter"; + private static final Log log = LogFactory.getLog(SearchFilter.class); + + private enum SortOption { + hitsCount, + labelText, + labelNumber, + idText, + idNumber + } private String id; private String name = ""; @@ -26,12 +44,11 @@ public class SearchFilter { private String to = ""; private String fromYear = ""; private String toYear = ""; - private boolean isPublic = false; private String min = "0"; private String max = "2000"; private int moreLimit = 30; - private int order = 0; + private int rank = 0; private String field = ""; private String endField = ""; private String inputText = ""; @@ -40,15 +57,17 @@ public class SearchFilter { private boolean selected = false; private boolean input = false; private Map values = new LinkedHashMap<>(); - private boolean inputRegex = false; - + private String regexPattern = DEFAULT_PATTERN; private boolean facetsRequired; - + private Order valueSortDirection = Order.ASC; private String type = FILTER; private String rangeText = ""; private String rangeInput = ""; - private boolean hidden = false; + private boolean displayed = false; + private Optional locale; + private boolean multilingual; + private SortOption sortOption = SortOption.labelText; public String getRangeInput() { return rangeInput; @@ -62,8 +81,9 @@ public String getRangeText() { return rangeText; } - public SearchFilter(String id) { + public SearchFilter(String id, Optional locale) { this.id = id; + this.locale = locale; } public String getName() { @@ -76,17 +96,24 @@ public void setName(RDFNode rdfNode) { } } - public void setOrder(RDFNode rdfNode) { + public void setRank(RDFNode rdfNode) { if (rdfNode != null) { - order = rdfNode.asLiteral().getInt(); + rank = rdfNode.asLiteral().getInt(); } } - public Integer getOrder() { - return order; + public Integer getRank() { + return rank; } public String getField() { + if (multilingual) { + if (locale.isPresent()) { + return locale.get().toLanguageTag() + field; + } else { + return Locale.getDefault().toLanguageTag() + field; + } + } return field; } @@ -269,32 +296,82 @@ public void setToYear(String toYear) { this.toYear = getYear(toYear); } - public void sortValues() { + public void sortValues(Collator collator) { List> list = new LinkedList<>(values.entrySet()); - list.sort(new FilterValueComparator()); - values = list.stream() - .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new)); + list.sort(new FilterValueComparator(collator)); + values = list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, + LinkedHashMap::new)); } - public boolean isPublic() { - return isPublic; - } + private class FilterValueComparator implements Comparator> { - public void setPublic(boolean isPublic) { - this.isPublic = isPublic; - } + Collator collator; + + public FilterValueComparator(Collator collator) { + this.collator = collator; + } - private class FilterValueComparator implements Comparator> { public int compare(Entry obj1, Entry obj2) { - FilterValue filter1 = obj1.getValue(); - FilterValue filter2 = obj2.getValue(); - int result = filter1.getOrder().compareTo(filter2.getOrder()); + FilterValue first = obj1.getValue(); + FilterValue second = obj2.getValue(); + // sort by order first + int result = first.getRank().compareTo(second.getRank()); if (result == 0) { - // order are equal, sort by name - return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); - } else { - return result; + result = compareByObjectType(first, second); + if (valueSortDirection.equals(Order.DESC)) { + result = -result; + } + } + return result; + } + + private int compareByObjectType(FilterValue first, FilterValue second) { + int result; + switch (sortOption) { + case hitsCount: + result = Long.compare(first.getCount(), second.getCount()); + break; + case labelNumber: + result = compareAsNumbers(first.getName(), second.getName()); + break; + case idText: + result = collator.compare(first.getId(), second.getId()); + break; + case idNumber: + result = compareAsNumbers(first.getId(), second.getId()); + break; + default: + // labelText + result = collator.compare(first.getName(), second.getName()); } + return result; + } + + private int compareAsNumbers(String first, String second) { + int result; + Double firstDouble = parseDouble(first); + Double secondDouble = parseDouble(second); + result = firstDouble.compareTo(secondDouble); + return result; + } + + private double parseDouble(String name) { + name = name + //replace all but digits, dots or minuses + .replaceAll("[^\\d.-]", "") + //replace all minuses but the one at start + .replaceAll("(? filters = new LinkedHashSet<>(); @@ -44,20 +43,12 @@ public void setLabel(String label) { this.label = label; } - public boolean isPublic() { - return isPublic; + public boolean isDisplayed() { + return displayed; } - public void setPublic(boolean isPublic) { - this.isPublic = isPublic; - } - - public boolean isHidden() { - return hidden; - } - - public void setHidden(boolean hidden) { - this.hidden = hidden; + public void setDisplayed(boolean display) { + this.displayed = display; } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java index a32e963a83..723000f50e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java @@ -1,19 +1,20 @@ package edu.cornell.mannlib.vitro.webapp.search.controller; import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.ROLE_PUBLIC_URI; +import static edu.cornell.mannlib.vitro.webapp.search.controller.PagedSearchController.PARAM_QUERY_TEXT; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -46,15 +47,18 @@ public class SearchFiltering { private static final String FILTER_RANGE = "filter_range_"; private static final String FILTER_INPUT_PREFIX = "filter_input_"; - private static final String FILTERS = "filters"; + public static final String FILTERS = "filters"; + public static final String ANY_VALUE = "[* TO *]"; private static final String FILTER_QUERY = "" + "PREFIX search: \n" + + "PREFIX search-ind:\n" + "PREFIX rdf: \n" + "PREFIX rdfs: \n" - + "SELECT ?filter_id ?filter_type ?filter_label ?value_label ?value_id ?field_name ?public ?filter_order " - + "?value_order (STR(?isUriReq) as ?isUri ) ?multivalued ?input ?regex ?facet ?min ?max ?role " - + "?value_public ?more_limit \n" + + "SELECT ?filter_id ?filter_type ?filter_label ?value_label ?value_id ?field_name ?public ?filter_rank " + + "?value_rank (STR(?isUriReq) as ?isUri ) ?multivalued ?input ?regex ?regexPattern ?facet ?min ?max ?role " + + "?value_public ?more_limit ?multilingual ?isDescending\n" + + "?filterDisplayLimitRole ?valueDisplayLimitRole ?sortingObjectType \n" + "WHERE {\n" + " ?filter rdf:type search:Filter .\n" + " ?filter rdfs:label ?filter_label .\n" @@ -67,8 +71,8 @@ public class SearchFiltering { + " ?value rdfs:label ?value_label .\n" + " ?value search:id ?value_id .\n" + " OPTIONAL {" - + " ?value search:order ?v_order .\n" - + " bind(?v_order as ?value_order_found).\n" + + " ?value search:order|search:rank ?v_rank .\n" + + " bind(?v_rank as ?value_rank_found).\n" + " }\n" + " OPTIONAL {\n" + " ?value search:isDefaultForRole ?role .\n" @@ -76,42 +80,59 @@ public class SearchFiltering { + " OPTIONAL {\n" + " ?value search:public ?value_public .\n" + " }\n" + + " OPTIONAL {?value search:limitDisplayTo ?valueDisplayLimitRole . }\n" + + " }\n" + + " OPTIONAL {\n" + + " ?field search:isLanguageSpecific ?f_multilingual .\n" + + " BIND(?f_multilingual as ?bind_multilingual) .\n" + " }\n" + " OPTIONAL {?field search:multivalued ?multivalued}\n" + " OPTIONAL {?filter search:isUriValues ?isUriReq }\n" + " OPTIONAL {?filter search:userInput ?input }\n" + " OPTIONAL {?filter search:userInputRegex ?regex }\n" + + " OPTIONAL {?filter search:regexPattern ?regexPattern }\n" + " OPTIONAL {?filter search:facetResults ?facet }\n" + + " OPTIONAL {?filter search:reverseFacetOrder ?isDescendingDeprecated }\n" + + " OPTIONAL {" + + " ?filter search:direction search-ind:descending ." + + " BIND(true as ?isDescendingNew)" + + " }\n" + + " OPTIONAL {?filter search:sortValuesBy ?sortingObjectType }\n" + " OPTIONAL {?filter search:from ?min }\n" + " OPTIONAL {?filter search:public ?public }\n" + " OPTIONAL {?filter search:to ?max }\n" + " OPTIONAL {?filter search:moreLimit ?more_limit }\n" + + " OPTIONAL {?filter search:limitDisplayTo ?filterDisplayLimitRole . }\n" + " OPTIONAL {\n" - + " ?filter search:order ?f_order \n" - + " bind(?f_order as ?filter_order_found).\n" + + " ?filter search:order|search:rank ?f_rank \n" + + " BIND(?f_rank as ?filter_rank_found).\n" + " }\n" - + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" - + " BIND(coalesce(?value_order_found, 0) as ?value_order)\n" - + "} ORDER BY ?filter_id ?filter_order ?value_order"; + + " BIND(COALESCE(?filter_rank_found, 0) as ?filter_rank)\n" + + " BIND(COALESCE(?value_rank_found, 0) as ?value_rank)\n" + + " BIND(COALESCE(?bind_multilingual, false) as ?multilingual)\n" + + " BIND(COALESCE(?isDescendingNew, ?isDescendingDeprecated, false) as ?isDescending)\n" + + "} ORDER BY ?filter_id ?filter_rank ?value_rank"; private static final String FILTER_GROUPS_QUERY = "" + "PREFIX rdf: \n" + "PREFIX search: \n" + "PREFIX rdfs: \n" - + "SELECT ?group_id (STR(?group_l) AS ?group_label) ?filter_id ?order ?filter_order ?public\n" + + "SELECT ?group_id (STR(?group_l) AS ?group_label) ?filter_id ?rank ?filter_rank ?public\n" + + "?groupDisplayLimitRole\n" + "WHERE {\n" + " ?filter_group rdf:type search:FilterGroup .\n" + " ?filter_group search:contains ?filter .\n" + " ?filter_group rdfs:label ?group_l .\n" + " ?filter_group search:id ?group_id .\n" - + " OPTIONAL { ?filter_group search:order ?order . } \n" + + " OPTIONAL {?filter_group search:order|search:rank ?rank .}\n" + " ?filter search:id ?filter_id .\n" + " OPTIONAL {?filter_group search:public ?public }\n" - + " OPTIONAL{ ?filter search:order ?f_order .\n" - + " bind(?f_order as ?filter_order_found).\n" + + " OPTIONAL {?filter search:order|search:rank ?f_rank .\n" + + " BIND(?f_rank as ?filter_rank_found).\n" + " }\n" - + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" - + "} ORDER BY ?order ?group_label ?filter_order"; + + " OPTIONAL {?filter_group search:limitDisplayTo ?groupDisplayLimitRole .}\n" + + " BIND(COALESCE(?filter_rank_found, 0) as ?filter_rank)\n" + + "} ORDER BY ?rank ?group_label ?filter_rank"; private static final String LABEL_QUERY = "" + "PREFIX rdfs: \n" @@ -124,8 +145,9 @@ public class SearchFiltering { + "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX search: \n" - + "SELECT ( STR(?sort_label) as ?label ) ?id ?searchField " + - "?multilingual ?isAsc ?sort_order ?fallback ?display\n" + + "PREFIX search-ind:\n" + + "SELECT ( STR(?sort_label) as ?label ) ?id ?searchField " + + "?multilingual ?isAscending ?sort_rank ?fallback ?display ?sortDisplayLimitRole\n" + "WHERE {\n" + " ?sort rdf:type search:Sort . \n" + " ?sort rdfs:label ?sort_label .\n" @@ -141,59 +163,43 @@ public class SearchFiltering { + " ?sort search:id ?id .\n" + " }\n" + " OPTIONAL {\n" - + " ?sort search:isAscending ?f_ord .\n" - + " BIND(?f_ord as ?f_order) .\n" + + " ?sort search:isAscending ?isAscendingDeprecated .\n" + + " }\n" + + " OPTIONAL {" + + " ?sort search:direction search-ind:ascending ." + + " BIND(true as ?isAscendingNew)" + " }\n" + " OPTIONAL {\n" + " ?sort search:hasFallback/search:id ?fallback .\n" + " }\n" + " OPTIONAL{ " - + " ?sort search:order ?s_order .\n" - + " BIND(?s_order as ?sort_order_found).\n" + + " ?sort search:order|search:rank ?s_rank .\n" + + " BIND(?s_rank as ?sort_rank_found).\n" + " }\n" + " OPTIONAL {?sort search:display ?display }\n" - + " BIND(coalesce(?sort_order_found, 0) as ?sort_order)\n" - + " BIND(COALESCE(?f_order, false) as ?isAsc)\n" + + " OPTIONAL {?sort search:limitDisplayTo ?sortDisplayLimitRole .}\n" + + " BIND(coalesce(?sort_rank_found, 0) as ?sort_rank)\n" + + " BIND(COALESCE(?isAscendingNew, ?isAscendingDeprecated, false) as ?isAscending)\n" + " BIND(COALESCE(?bind_multilingual, false) as ?multilingual)\n" - + "} ORDER BY ?sort_order ?label "; - - protected static void addFiltersToQuery(VitroRequest vreq, SearchQuery query, - Map filterById) { - Enumeration paramNames = vreq.getParameterNames(); - while (paramNames.hasMoreElements()) { - String paramFilterName = paramNames.nextElement(); - if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { - String[] filters = vreq.getParameterValues(paramFilterName); - if (filters != null && filters.length > 0) { - for (String filter : filters) { - String[] pair = filter.split(":", 2); - if (pair.length == 2) { - String name = pair[0].replace("\"", ""); - String value = pair[1].replace("\"", ""); - SearchFilter searchFilter = filterById.get(name); - if (searchFilter != null && searchFilter.getField() != null) { - query.addFilterQuery(searchFilter.getField() + ":\"" + value + "\""); - } - - } - } - } + + "} ORDER BY ?sort_rank ?label "; + protected static void addFiltersToQuery(SearchQuery query, Map filters) { + for (SearchFilter searchFilter : filters.values()) { + if (PARAM_QUERY_TEXT.equals(searchFilter.getId())) { + continue; } - } - addPreconfiguredFiltersToQuery(query, filterById.values()); - } - - public static void addPreconfiguredFiltersToQuery(SearchQuery query, Collection collection) { - for (SearchFilter searchFilter : collection) { if (searchFilter.isInput()) { SearchFiltering.addInputFilter(query, searchFilter); } else if (searchFilter.isRange()) { SearchFiltering.addRangeFilter(query, searchFilter); } for (FilterValue fv : searchFilter.getValues().values()) { - if (fv.isDefault()) { - query.addFilterQuery(searchFilter.getField() + ":\"" + fv.getId() + "\""); + if (fv.isDefault() || fv.isSelected()) { + if (ANY_VALUE.equals(fv.getId())) { + query.addFilterQuery(searchFilter.getField() + ":" + ANY_VALUE ); + } else { + query.addFilterQuery(searchFilter.getField() + ":\"" + fv.getId() + "\""); + } } } } @@ -204,33 +210,51 @@ public static Map> getRequestFilters(VitroRequest vreq) { Enumeration paramNames = vreq.getParameterNames(); while (paramNames.hasMoreElements()) { String paramFilterName = paramNames.nextElement(); - if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { - String[] filters = vreq.getParameterValues(paramFilterName); - if (filters != null && filters.length > 0) { - for (String filter : filters) { + if (paramFilterName.startsWith(SearchFiltering.FILTERS)) { + String[] filterValues = vreq.getParameterValues(paramFilterName); + if (filterValues != null && filterValues.length > 0) { + for (String filter : filterValues) { String[] pair = filter.split(":", 2); if (pair.length == 2) { - String name = pair[0].replace("\"", ""); + String filterId = pair[0].replace("\"", ""); String value = pair[1].replace("\"", ""); - if (requestFilters.containsKey(name)) { - List list = requestFilters.get(name); + if (requestFilters.containsKey(filterId)) { + List list = requestFilters.get(filterId); list.add(value); } else { - requestFilters.put(name, new LinkedList(Arrays.asList(value))); + requestFilters.put(filterId, new LinkedList(Arrays.asList(value))); } } } } } + if (paramFilterName.startsWith(SearchFiltering.FILTER_RANGE)) { + String[] values = vreq.getParameterValues(paramFilterName); + if (values != null && values.length > 0) { + String filterId = paramFilterName.replace(SearchFiltering.FILTER_RANGE, ""); + requestFilters.put(filterId, new LinkedList(Arrays.asList(values[0]))); + } + } + if (paramFilterName.startsWith(SearchFiltering.FILTER_INPUT_PREFIX)) { + String[] values = vreq.getParameterValues(paramFilterName); + if (values != null && values.length > 0) { + String filterId = paramFilterName.replace(SearchFiltering.FILTER_INPUT_PREFIX, ""); + requestFilters.put(filterId, new LinkedList(Arrays.asList(values[0]))); + } + } + if (paramFilterName.equals(PARAM_QUERY_TEXT)) { + String[] values = vreq.getParameterValues(paramFilterName); + if (values != null && values.length > 0) { + requestFilters.put(PARAM_QUERY_TEXT, new LinkedList(Arrays.asList(values[0]))); + } + } } - return requestFilters; } public static Map readFilterConfigurations(Set currentRoles, VitroRequest vreq) { long startTime = System.nanoTime(); - - Map filtersByField = new LinkedHashMap<>(); + Map filters = new LinkedHashMap<>(); Model model; if (vreq != null) { model = ModelAccess.on(vreq).getOntModelSelector().getDisplayModel(); @@ -238,7 +262,7 @@ public static Map readFilterConfigurations(Set cur model = ModelAccess.getInstance().getOntModelSelector().getDisplayModel(); } if (model == null) { - return filtersByField; + return filters; } model.enterCriticalSection(Lock.READ); try { @@ -247,19 +271,26 @@ public static Map readFilterConfigurations(Set cur ResultSet results = qexec.execSelect(); while (results.hasNext()) { QuerySolution solution = results.nextSolution(); - if (solution.get("filter_id") == null || solution.get("field_name") == null - || solution.get("filter_type") == null) { + if (solution.get("filter_id") == null || + solution.get("field_name") == null || + solution.get("filter_type") == null) { continue; } - String resultFilterId = solution.get("filter_id").toString(); + String filterId = solution.get("filter_id").toString(); String resultFieldName = solution.get("field_name").toString(); SearchFilter filter = null; - if (filtersByField.containsKey(resultFieldName)) { - filter = filtersByField.get(resultFieldName); + if (filters.containsKey(filterId)) { + filter = filters.get(filterId); } else { - filter = createSearchFilter(filtersByField, solution, resultFilterId, resultFieldName); + Optional locale = vreq != null ? Optional.of(vreq.getLocale()) : Optional.empty(); + filter = createSearchFilter(filters, solution, filterId, resultFieldName, locale); + } + if (isDisplay(solution, vreq, "filterDisplayLimitRole", "public")) { + filter.setDisplayed(true); } + filter.setType(solution.get("filter_type")); + filter.setMulitlingual(solution.get("multilingual").asLiteral().getBoolean()); if (solution.get("value_id") == null) { continue; } @@ -268,11 +299,13 @@ public static Map readFilterConfigurations(Set cur if (!filter.contains(valueId)) { value = new FilterValue(valueId); value.setName(solution.get("value_label")); - value.setOrder(solution.get("value_order")); - value.setPubliclyAvailable(solution.get("value_public")); + value.setRank(solution.get("value_rank")); filter.addValue(value); } value = filter.getValue(valueId); + if (isDisplay(solution, vreq, "valueDisplayLimitRole", "value_public")) { + value.setDisplayed(true); + } RDFNode role = solution.get("role"); if (role != null && role.isResource()) { String roleUri = role.asResource().getURI(); @@ -287,12 +320,57 @@ public static Map readFilterConfigurations(Set cur if (log.isDebugEnabled()) { log.debug(getSpentTime(startTime) + "ms spent after FILTER QUERY request."); } - return sortFilters(filtersByField); + return sortFilters(filters); + } + + private static boolean isDisplay(QuerySolution solution, VitroRequest vreq, String limitVarName, + String publicVarName) { + // Display if user is root + if (isRoot(vreq)) { + return true; + } + //Display if public set to true + RDFNode nodePublic = solution.get(publicVarName); + if (nodePublic != null && nodePublic.isLiteral() && nodePublic.asLiteral().getBoolean()) { + return nodePublic.asLiteral().getBoolean(); + } + RDFNode limitToRole = solution.get(limitVarName); + Set roles = getCurrentUserRoles(vreq); + // nodeLimit is not set and user is not Public, then display + if (limitToRole == null && !roles.contains(ROLE_PUBLIC_URI)) { + return true; + } + // node limit is set and current user has that role + if (limitToRole != null && roles.contains(getNodeStringValue(limitToRole))) { + return true; + } + return false; + } + + private static String getNodeStringValue(RDFNode node) { + String value; + if (node.isResource()) { + value = node.asResource().getURI(); + } else { + value = node.asLiteral().getLexicalForm(); + } + return value; } public static void addDefaultFilters(SearchQuery query, Set currentRoles) { - Map filtersByField = SearchFiltering.readFilterConfigurations(currentRoles, null); - SearchFiltering.addPreconfiguredFiltersToQuery( query, filtersByField.values()); + Map filters = SearchFiltering.readFilterConfigurations(currentRoles, null); + for (SearchFilter searchFilter : filters.values()) { + if (searchFilter.isInput()) { + SearchFiltering.addInputFilter(query, searchFilter); + } else if (searchFilter.isRange()) { + SearchFiltering.addRangeFilter(query, searchFilter); + } + for (FilterValue fv : searchFilter.getValues().values()) { + if (fv.isDefault()) { + query.addFilterQuery(searchFilter.getField() + ":\"" + fv.getId() + "\""); + } + } + } } public static Map sortFilters(Map filters) { @@ -305,9 +383,9 @@ public static class FilterComparator implements Comparator obj1, Entry obj2) { SearchFilter filter1 = obj1.getValue(); SearchFilter filter2 = obj2.getValue(); - int result = filter1.getOrder().compareTo(filter2.getOrder()); + int result = filter1.getRank().compareTo(filter2.getRank()); if (result == 0) { - // order are equal, sort by name + // ranks are equal, sort by name return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); } else { return result; @@ -326,8 +404,9 @@ public static List readFilterGroupsConfigurations(VitroReques ResultSet results = qexec.execSelect(); while (results.hasNext()) { QuerySolution solution = results.nextSolution(); - if (solution.get("filter_id") == null || solution.get("group_label") == null - || solution.get("group_id") == null) { + if (solution.get("filter_id") == null || + solution.get("group_label") == null || + solution.get("group_id") == null) { continue; } String filterId = solution.get("filter_id").toString(); @@ -338,17 +417,12 @@ public static List readFilterGroupsConfigurations(VitroReques group = groups.get(groupId); } else { group = new SearchFilterGroup(groupId, groupLabel); - RDFNode publicNode = solution.get("public"); - if (publicNode != null) { - group.setPublic(publicNode.asLiteral().getBoolean()); - } groups.put(groupId, group); } - group.addFilterId(filterId); - SearchFilter filter = filtersById.get(filterId); - if (filter != null && !filter.isHidden()) { - group.setHidden(false); + if (isDisplay(solution, vreq, "groupDisplayLimitRole", "public")) { + group.setDisplayed(true); } + group.addFilterId(filterId); } } finally { model.leaveCriticalSection(); @@ -382,21 +456,21 @@ public static Map getSortConfigurations(VitroRequest if (multilingual != null) { config.setMultilingual(multilingual.asLiteral().getBoolean()); } - RDFNode isAsc = solution.get("isAsc"); - if (isAsc != null) { - config.setAscOrder(isAsc.asLiteral().getBoolean()); + RDFNode isAscending = solution.get("isAscending"); + if (isAscending != null) { + config.setSortDirection(isAscending.asLiteral().getBoolean()); } RDFNode fallback = solution.get("fallback"); if (fallback != null && fallback.isLiteral()) { config.setFallback(fallback.asLiteral().toString()); } - RDFNode order = solution.get("sort_order"); - if (order != null) { - config.setOrder(order.asLiteral().getInt()); + RDFNode rank = solution.get("sort_rank"); + if (rank != null) { + config.setRank(rank.asLiteral().getInt()); } RDFNode display = solution.get("display"); if (display != null) { - config.setDisplay(display.asLiteral().getBoolean()); + config.setDisplayed(display.asLiteral().getBoolean()); } sortConfigurations.put(id, config); } @@ -407,13 +481,13 @@ public static Map getSortConfigurations(VitroRequest return sortConfigurations; } - private static SearchFilter createSearchFilter(Map filtersByField, - QuerySolution solution, String resultFilterId, String resultFieldName) { + private static SearchFilter createSearchFilter(Map filters, + QuerySolution solution, String filterId, String resultFieldName, Optional locale) { SearchFilter filter; - filter = new SearchFilter(resultFilterId); - filtersByField.put(resultFieldName, filter); + filter = new SearchFilter(filterId, locale); + filters.put(filterId, filter); filter.setName(solution.get("filter_label")); - filter.setOrder(solution.get("filter_order")); + filter.setRank(solution.get("filter_rank")); filter.setType(solution.get("filter_type")); if (solution.get("isUri") != null && "true".equals(solution.get("isUri").toString())) { filter.setLocalizationRequired(true); @@ -439,17 +513,26 @@ private static SearchFilter createSearchFilter(Map filters if (inputRegex != null) { filter.setInputRegex(inputRegex.asLiteral().getBoolean()); } - - RDFNode publicNode = solution.get("public"); - if (publicNode != null) { - filter.setPublic(publicNode.asLiteral().getBoolean()); - } - RDFNode facet = solution.get("facet"); if (facet != null) { filter.setFacetsRequired(facet.asLiteral().getBoolean()); } + RDFNode descendingOrder = solution.get("isDescending"); + if (descendingOrder != null && descendingOrder.isLiteral()) { + filter.setValueSortDirection(descendingOrder.asLiteral().getBoolean()); + } + + RDFNode sortOption = solution.get("sortingObjectType"); + if (sortOption != null && sortOption.isURIResource()) { + filter.setSortOption(sortOption.asResource().getURI()); + } + + RDFNode regexPattern = solution.get("regexPattern"); + if (regexPattern != null && regexPattern.isLiteral()) { + filter.setRegexPattern(regexPattern.asLiteral().getLexicalForm()); + } + RDFNode moreLimit = solution.get("more_limit"); if (moreLimit != null && moreLimit.isLiteral()) { filter.setMoreLimit(moreLimit.asLiteral().getInt()); @@ -470,12 +553,10 @@ private static void addInputFilter(SearchQuery query, SearchFilter searchFilter) || PagedSearchController.PARAM_QUERY_TEXT.equals(searchFilter.getId())) { return; } - String searchText = searchFilter.getInputText(); if (searchFilter.isInputRegex()) { - searchText = searchText.replaceAll("([ )(:])", "\\\\$1") + "*"; - query.addFilterQuery(searchFilter.getField() + ":" + searchText); + query.addFilterQuery(searchFilter.getField() + ":/" + searchFilter.getInputRegex() + "/"); } else { - query.addFilterQuery(searchFilter.getField() + ":\"" + searchText + "\""); + query.addFilterQuery(searchFilter.getField() + ":\"" + searchFilter.getInputText() + "\""); } } @@ -498,17 +579,27 @@ static String getFilterRangeText(VitroRequest vreq, String name) { return ""; } - public static void setSelectedFilters(Map filtersByField, - Map> requestFilters) { - for (SearchFilter filter : filtersByField.values()) { + public static void setSelectedFilters(Map filters, Map> requestFilters) { + for (SearchFilter filter : filters.values()) { if (requestFilters.containsKey(filter.getId())) { List requestValues = requestFilters.get(filter.getId()); if (!SearchFiltering.isEmptyValues(requestValues)) { filter.setSelected(true); - for (String requestValue : requestValues) { - if (filter.getValues().containsKey(requestValue)) { - FilterValue value = filter.getValue(requestValue); - value.setSelected(true); + if (filter.isRange()) { + filter.setRangeValues(requestValues.iterator().next()); + } else if (filter.isInput()) { + filter.setInputText(requestValues.iterator().next()); + } else { + for (String requestValue : requestValues) { + if (filter.getValues().containsKey(requestValue)) { + FilterValue value = filter.getValue(requestValue); + value.setSelected(true); + } else { + FilterValue value = new FilterValue(requestValue); + value.setSelected(true); + value.setDisplayed(true); + filter.addValue(value); + } } } } @@ -524,6 +615,14 @@ public static Set getCurrentUserRoles(VitroRequest vreq) { return user.getPermissionSetUris(); } + public static boolean isRoot(VitroRequest vreq) { + UserAccount user = LoginStatusBean.getCurrentUser(vreq); + if (user == null) { + return false; + } + return user.isRootUser(); + } + static boolean isEmptyValues(List requestedValues) { if (requestedValues.isEmpty()) { return true; @@ -563,31 +662,14 @@ static String getUriLabel(String uri, VitroRequest vreq) { } static void addFacetFieldsToQuery(Map filters, SearchQuery query) { - for (String fieldId : filters.keySet()) { - SearchFilter filter = filters.get(fieldId); + for (SearchFilter filter : filters.values()) { if (filter.isFacetsRequired()) { - query.addFacetFields(fieldId); + query.addFacetFields(filter.getField()); } } } public static Map getFiltersById(Map filtersByField) { - Map filtersById = - filtersByField.values().stream().collect(Collectors.toMap(SearchFilter::getId, Function.identity())); - return filtersById; - } - - static Map getFiltersForTemplate(Map filtersByField) { - Iterator> iterator = filtersByField.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - SearchFilter searchFilter = entry.getValue(); - searchFilter.removeValuesWithZeroCount(); - if (searchFilter.isEmpty()) { - searchFilter.setHidden(true); - } - - } return filtersByField.values().stream().collect(Collectors.toMap(SearchFilter::getId, Function.identity())); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java index 5650ec252d..a251e6255e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java @@ -1,5 +1,8 @@ package edu.cornell.mannlib.vitro.webapp.search.controller; +import static edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery.Order.ASC; +import static edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery.Order.DESC; + import java.util.Locale; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery.Order; @@ -9,12 +12,12 @@ public class SortConfiguration { private String id = ""; private String field = ""; private boolean multilingual = false; - private boolean ascOrder = false; + private Order sortDirection = DESC; private boolean selected = false; private String label = ""; - private int order = 0; + private int rank = 0; private String fallback; - private boolean display = false; + private boolean displayed = false; public SortConfiguration(String id, String label, String field) { this.id = id; @@ -62,15 +65,16 @@ public String getField(Locale locale) { return field; } - public Order getSortOrder() { - if (ascOrder) { - return Order.ASC; - } - return Order.DESC; + public Order getSortDirection() { + return sortDirection; } - public void setAscOrder(boolean ascOrder) { - this.ascOrder = ascOrder; + public void setSortDirection(boolean isAscending) { + if (isAscending) { + sortDirection = ASC; + } else { + sortDirection = DESC; + } } public boolean isSelected() { @@ -81,12 +85,12 @@ public void setSelected(boolean selected) { this.selected = selected; } - public int getOrder() { - return order; + public int getRank() { + return rank; } - public void setOrder(int order) { - this.order = order; + public void setRank(int rank) { + this.rank = rank; } public String getFallback() { @@ -97,11 +101,11 @@ public void setFallback(String id) { this.fallback = id; } - public void setDisplay(boolean display) { - this.display = display; + public void setDisplayed(boolean display) { + this.displayed = display; } - public boolean isDisplay() { - return display; + public boolean isDisplayed() { + return displayed; } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java index 83f6b5b343..b6b9018d15 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java @@ -27,6 +27,8 @@ public class BaseSearchQuery implements SearchQuery { private final Set facetFields = new HashSet<>(); private int facetLimit = 100; private int facetMinCount = -1; + private boolean facetTextToCompareIgnoreCase; + private String facetContainsText; @Override public SearchQuery setQuery(String query) { @@ -147,4 +149,26 @@ public String toString() { + ", facetMinCount=" + facetMinCount + "]"; } + @Override + public SearchQuery setFacetTextToMatch(String text) { + facetContainsText = text; + return this; + } + + @Override + public String getFacetTextToMatch() { + return facetContainsText; + } + + @Override + public SearchQuery setFacetTextCompareCaseInsensitive(boolean b) { + facetTextToCompareIgnoreCase = b; + return this; + } + + @Override + public boolean isFacetTextCompareCaseInsensitive() { + return facetTextToCompareIgnoreCase; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrConversionUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrConversionUtils.java index 7d128644c0..72cc845a6e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrConversionUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrConversionUtils.java @@ -4,6 +4,8 @@ import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXT; import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXTUNSTEMMED; +import static org.apache.solr.common.params.FacetParams.FACET_CONTAINS; +import static org.apache.solr.common.params.FacetParams.FACET_CONTAINS_IGNORE_CASE; import java.util.ArrayList; import java.util.Collection; @@ -11,13 +13,13 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrQuery.ORDER; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputField; - import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputDocument; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputField; @@ -164,6 +166,16 @@ static SolrQuery convertToSolrQuery(SearchQuery query) { solrQuery.setFacetMinCount(minCount); } + String facetText = query.getFacetTextToMatch(); + if (StringUtils.isNotBlank(facetText)) { + solrQuery.setParam(FACET_CONTAINS, facetText); + } + + boolean facetContainsIgnoreCase = query.isFacetTextCompareCaseInsensitive(); + if (facetContainsIgnoreCase) { + solrQuery.setParam(FACET_CONTAINS_IGNORE_CASE, facetContainsIgnoreCase); + } + return solrQuery; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java index ff63a1c88d..cf20883a3f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java @@ -66,6 +66,8 @@ public class SelectQueryDocumentModifier implements DocumentModifier, */ private List fieldNames = new ArrayList<>(); + protected List varNames = new ArrayList<>(); + /** * URIs of the types of individuals to whom these queries apply. If empty, * then the queries apply to all individuals. @@ -92,6 +94,11 @@ public void addTargetField(String fieldName) { fieldNames.add(fieldName); } + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasVar") + public void addVar(String varName) { + varNames.add(varName); + } + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTypeRestriction") public void addTypeRestriction(String typeUri) { typeRestrictions.add(typeUri); @@ -154,7 +161,10 @@ private List getTextForQuery(String query, Individual ind) { QueryHolder queryHolder = new QueryHolder(query).bindToUri("uri", ind.getURI()); List list = createSelectQueryContext(rdfService, - queryHolder).execute().toStringFields().flatten(); + queryHolder) + .execute() + .toStringFields(varNames.toArray(new String[varNames.size()])) + .flatten(); log.debug(label + " - query: '" + query + "' returns " + list); return list; } catch (Throwable t) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java index 89d81ebcfb..5a97bcdf66 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java @@ -78,7 +78,10 @@ protected Map> getTextForQuery(String query, Individual ind for (String locale : locales) { LanguageFilteringRDFService lfrs = new LanguageFilteringRDFService(rdfService, Collections.singletonList(locale)); - List list = createSelectQueryContext(lfrs, queryHolder).execute().toStringFields().flatten(); + List list = createSelectQueryContext(lfrs, queryHolder) + .execute() + .toStringFields(varNames.toArray(new String[varNames.size()])) + .flatten(); mapLocaleToFields.put(locale, list); log.debug(label + " for locale " + locale + " - query: '" + query + "' returns " + list); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java index 2fc1a35ee1..1839cacfce 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java @@ -16,7 +16,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.apache.jena.ontology.OntModel; import org.apache.jena.query.Query; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -447,8 +447,38 @@ public static void getClassGroupForDataGetter(HttpServletRequest req, Map \n" + + "SELECT ?filterUri ?filterName WHERE {\n" + + " ?filterUri a search:Filter .\n" + + " ?filterUri search:facetResults true .\n" + + " ?filterUri <" + VitroVocabulary.LABEL + "> ?filterName .\n" + + "}"; + + public static Object getSearchFilters(VitroRequest vreq) { + OntModel model = vreq.getDisplayModel(); + List> filters = new ArrayList>(); + Query q = QueryFactory.create(searchFiltersQuery); + QuerySolutionMap bindings = new QuerySolutionMap(); + model.enterCriticalSection(Lock.READ); + try (QueryExecution qexec = QueryExecutionFactory.create(q, model, bindings);) { + ResultSet res = qexec.execSelect(); + while (res.hasNext()) { + QuerySolution sol = res.next(); + Resource uri = sol.getResource("filterUri"); + Literal name = sol.getLiteral("filterName"); + HashMap filter = new HashMap(); + filter.put("URI", uri.toString()); + filter.put("publicName", name.getLexicalForm()); + filters.add(filter); + } + } catch (Exception e) { + log.error(e, e); + } finally { + model.leaveCriticalSection(); + } + return filters; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SearchFilterValuesDataGetter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SearchFilterValuesDataGetter.java new file mode 100644 index 0000000000..33d59840bb --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SearchFilterValuesDataGetter.java @@ -0,0 +1,126 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ +package edu.cornell.mannlib.vitro.webapp.utils.dataGetter; + +import static edu.cornell.mannlib.vitro.webapp.search.controller.SearchFiltering.ANY_VALUE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.DisplayVocabulary; +import edu.cornell.mannlib.vitro.webapp.search.controller.PagedSearchController; +import edu.cornell.mannlib.vitro.webapp.search.controller.SearchFiltering; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.QuerySolutionMap; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.shared.Lock; +import org.apache.tika.utils.StringUtils; + +public class SearchFilterValuesDataGetter extends DataGetterBase implements DataGetter { + private static final String defaultTemplate = "browseSearchFilterValues.ftl"; + private static final String searchFilterUri = "<" + DisplayVocabulary.SEARCH_FILTER_VALUE + ">"; + private static final Log log = LogFactory.getLog(SearchFilterValuesDataGetter.class); + + private String dataGetterURI; + private String searchFilter; + private VitroRequest vreq; + + /** + * Constructor with display model and data getter URI that will be called by reflection. + */ + public SearchFilterValuesDataGetter(VitroRequest vreq, Model displayModel, String dataGetterURI) { + this.configure(vreq, displayModel, dataGetterURI); + } + + @Override + public Map getData(Map pageData) { + Map responseMap = new HashMap<>(); + responseMap.putAll(vreq.getParameterMap()); + Map> requestFilters = SearchFiltering.getRequestFilters(vreq); + if (!isValidFilterValueProvided(requestFilters)) { + requestFilters.put(searchFilter, new ArrayList(Arrays.asList(ANY_VALUE))); + } + responseMap.putAll(PagedSearchController.process(vreq, requestFilters).getMap()); + responseMap.put("searchFilter", this.searchFilter); + responseMap.put("bodyTemplate", defaultTemplate); + responseMap.put("languageAware", isLanguageAwarenessEnabled()); + return responseMap; + } + + private Boolean isLanguageAwarenessEnabled() { + ConfigurationProperties cp = ConfigurationProperties.getInstance(); + return Boolean.valueOf(cp.getProperty("RDFService.languageFilter", "false")); + } + + private boolean isValidFilterValueProvided(Map> requestFilters) { + if (!requestFilters.containsKey(searchFilter)) { + return false; + } + List values = requestFilters.get(searchFilter); + for (String value : values) { + if (!StringUtils.isBlank(value)) { + return true; + } + } + return true; + } + + /** + * Configure this instance based on the URI and display model. + */ + protected void configure(VitroRequest vreq, Model displayModel, String dataGetterURI) { + if (vreq == null) { + throw new IllegalArgumentException("VitroRequest may not be null."); + } + if (displayModel == null) { + throw new IllegalArgumentException("Display Model may not be null."); + } + if (dataGetterURI == null) { + throw new IllegalArgumentException("PageUri may not be null."); + } + + this.vreq = vreq; + this.dataGetterURI = dataGetterURI; + + QuerySolutionMap initBindings = new QuerySolutionMap(); + initBindings.add("dataGetterURI", ResourceFactory.createResource(this.dataGetterURI)); + + Query configurationQuery = QueryFactory.create(dataGetterQuery); + displayModel.enterCriticalSection(Lock.READ); + try (QueryExecution qexec = QueryExecutionFactory.create(configurationQuery, displayModel, initBindings)) { + ResultSet res = qexec.execSelect(); + while (res.hasNext()) { + QuerySolution soln = res.next(); + this.searchFilter = soln.getLiteral("id").toString(); + } + } finally { + displayModel.leaveCriticalSection(); + } + } + + public static final String defaultVarNameForResults = "results"; + + /** + * Query to get search filter id for a given URI. + */ + private static final String dataGetterQuery = + "PREFIX display: <" + DisplayVocabulary.DISPLAY_NS + "> \n" + + "PREFIX search: \n" + + "SELECT ?id WHERE { \n" + + "?dataGetterURI " + searchFilterUri + " ?searchFilter .\n" + + "?searchFilter search:id ?id .\n" + + "}"; + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java index df25eba9cb..69881b18ba 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java @@ -28,6 +28,11 @@ public class IndividualSearchResult extends BaseTemplateModel { protected final VitroRequest vreq; protected final Individual individual; + public String getThumbUrl() { + String thumbUrl = individual.getThumbUrl(); + return thumbUrl == null ? null : getUrl(thumbUrl); + } + public IndividualSearchResult(Individual individual, VitroRequest vreq) { this.vreq = vreq; this.individual = individual; diff --git a/home/src/main/resources/rdf/display/everytime/dataGetterLabels.n3 b/home/src/main/resources/rdf/display/everytime/dataGetterLabels.n3 index a53effd4fe..763f9773e9 100644 --- a/home/src/main/resources/rdf/display/everytime/dataGetterLabels.n3 +++ b/home/src/main/resources/rdf/display/everytime/dataGetterLabels.n3 @@ -6,6 +6,7 @@ @prefix rdfs: . rdfs:label "Class Group Page" . + rdfs:label "Search filter values" . rdfs:label "Browse Page" . rdfs:label "Class Group Page - Selected Classes" . rdfs:label "Sparql Query Results" . diff --git a/home/src/main/resources/rdf/display/firsttime/search_individuals_vitro.n3 b/home/src/main/resources/rdf/display/firsttime/search_individuals_vitro.n3 index 444c16e128..56e7715c38 100644 --- a/home/src/main/resources/rdf/display/firsttime/search_individuals_vitro.n3 +++ b/home/src/main/resources/rdf/display/firsttime/search_individuals_vitro.n3 @@ -4,9 +4,9 @@ @prefix rdfs: . :filter_group_search_filters a vitro-search:FilterGroup ; - vitro-search:contains :filter_category , :filter_type , :filter_querytext ; + vitro-search:contains :filter_category , :filter_type ; vitro-search:id "main" ; - vitro-search:order 1 ; + vitro-search:rank 1 ; vitro-search:public true . :filter_type a vitro-search:Filter ; @@ -14,7 +14,7 @@ vitro-search:filterField :field_type ; vitro-search:id "type" ; vitro-search:isUriValues true ; - vitro-search:order 30 ; + vitro-search:rank 30 ; vitro-search:public false . :filter_category @@ -25,45 +25,75 @@ vitro-search:isUriValues true ; vitro-search:public true . +:filter_raw_label_regex + a vitro-search:Filter ; + rdfs:label "Label regular expression"@en-US ; + vitro-search:facetResults false ; + vitro-search:filterField :field_name_lowercase_single_valued ; + vitro-search:id "raw_initial" ; + vitro-search:public true ; + vitro-search:userInput true ; + vitro-search:regexPattern "$0.*" ; + vitro-search:userInputRegex true . + +:filter_label_regex + a vitro-search:Filter ; + rdfs:label "Label regular expression"@en-US ; + vitro-search:facetResults false ; + vitro-search:filterField :field_label_sort ; + vitro-search:id "initial" ; + vitro-search:public true ; + vitro-search:userInput true ; + vitro-search:regexPattern "$0.*" ; + vitro-search:userInputRegex true . + + :filter_querytext a vitro-search:Filter ; - vitro-search:order 1 ; + vitro-search:rank 1 ; vitro-search:filterField :field_querytext ; + vitro-search:userInput true ; vitro-search:id "querytext" ; vitro-search:public true . +:ascending + a vitro-search:SortDirection . + +:descending + a vitro-search:SortDirection . + :sort_title_desc a vitro-search:Sort ; - vitro-search:isAscending false ; + vitro-search:direction :descending ; vitro-search:hasFallback :sort_name_raw_desc ; - vitro-search:order 30 ; + vitro-search:rank 30 ; vitro-search:sortField :field_label_sort ; vitro-search:display true ; vitro-search:id "titledesc" . :sort_title_asc a vitro-search:Sort ; - vitro-search:isAscending true ; + vitro-search:direction :ascending ; vitro-search:hasFallback :sort_name_raw_asc ; - vitro-search:order 20 ; + vitro-search:rank 20 ; vitro-search:sortField :field_label_sort ; vitro-search:display true ; vitro-search:id "titleasc" . :sort_name_raw_desc a vitro-search:Sort ; - vitro-search:isAscending false ; - vitro-search:order 50 ; + vitro-search:direction :descending ; + vitro-search:rank 50 ; vitro-search:sortField :field_name_raw ; vitro-search:id "name_raw_desc" . :sort_name_raw_asc a vitro-search:Sort ; - vitro-search:isAscending true ; - vitro-search:order 40 ; + vitro-search:direction :ascending ; + vitro-search:rank 40 ; vitro-search:sortField :field_name_raw ; vitro-search:id "name_raw_asc" . :sort_by_relevance a vitro-search:Sort ; vitro-search:sortField :field_score ; vitro-search:display true ; - vitro-search:order 60 ; + vitro-search:rank 60 ; vitro-search:id "relevance" . :field_score @@ -75,19 +105,45 @@ vitro-search:isLanguageSpecific true ; vitro-search:indexField "_label_sort" . +:field_label_display + a vitro-search:SearchField ; + vitro-search:isLanguageSpecific true ; + vitro-search:indexField "_label_display" . + :field_name_raw a vitro-search:SearchField ; vitro-search:indexField "nameRaw" . +:field_name_lowercase_single_valued + a vitro-search:SearchField ; + vitro-search:indexField "nameLowercaseSingleValued" . + + + :field_category a vitro-search:SearchField ; vitro-search:indexField "classgroup" . :field_type a vitro-search:SearchField ; vitro-search:indexField "type" ; - vitro-search:isLanguageSpecific true ; vitro-search:multivalued true . :field_querytext a vitro-search:SearchField ; vitro-search:indexField "querytext" . + +#Sort by number of found results associated with value +:hitsCount + a vitro-search:SortingObjectType . +#Sort by label text +:labelText + a vitro-search:SortingObjectType . +#Sort by numbers in label +:labelNumber + a vitro-search:SortingObjectType . +#Sort by id text +:idText + a vitro-search:SortingObjectType . +#Sort by numbers in id +:idNumber + a vitro-search:SortingObjectType . diff --git a/home/src/main/resources/rdf/displayTbox/everytime/displayTBOX.n3 b/home/src/main/resources/rdf/displayTbox/everytime/displayTBOX.n3 index e2764a5bc3..8b0d940d27 100644 --- a/home/src/main/resources/rdf/displayTbox/everytime/displayTBOX.n3 +++ b/home/src/main/resources/rdf/displayTbox/everytime/displayTBOX.n3 @@ -28,6 +28,8 @@ display:HomePage a owl:Class . display:ClassGroupPage a owl:Class . +display:SearchFilterValuesDataGetter a owl:Class . + display:IndividualsForClassesPage a owl:Class . display:InternalClassesPage a owl:Class . @@ -57,6 +59,10 @@ display:RequiredAction a owl:Class ; a owl:Class ; rdfs:comment "A data getter for a VClassGroup page" . + + a owl:Class ; + rdfs:comment "A data getter for a search filter values page" . + a owl:Class ; rdfs:comment "A data getter for a Fixed piece of HTML stored in RDF" . diff --git a/home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttime/vitro_UiLabel.ttl index 170ee62286..96cbf8fd88 100644 --- a/home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,28 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"@de-DE ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Ergebnisse der Suchfilterung durchsuchen"@de-DE ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Suchfilter auswählen"@de-DE ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/en_CA/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/en_CA/interface-i18n/firsttime/vitro_UiLabel.ttl index ad4069f169..c92aa00902 100644 --- a/home/src/main/resources/rdf/i18n/en_CA/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/en_CA/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,28 @@ uil-data:suppress_operation_for_unrelated_individuals_of_this_class.Vitro uil:hasApp "Vitro" ; uil:hasKey "suppress_operation_for_unrelated_individuals_of_this_class" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"@en-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Browse search filter results"@en-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Select search filter"@en-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/en_US/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/en_US/interface-i18n/firsttime/vitro_UiLabel.ttl index f8614c388b..db6ec1f37a 100644 --- a/home/src/main/resources/rdf/i18n/en_US/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/en_US/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -89,7 +89,7 @@ uil-data:accounts_search_results.Vitro uil-data:search_individual_results.Vitro rdf:type owl:NamedIndividual ; rdf:type uil:UILabel ; - rdfs:label "Search Class Individuals"@en-US ; + rdfs:label "Search class individuals"@en-US ; uil:hasApp "Vitro" ; uil:hasKey "search_individual_results" ; uil:hasPackage "Vitro-languages" . @@ -1353,7 +1353,7 @@ uil-data:close_capitalized.Vitro uil-data:browse_class_group.Vitro rdf:type owl:NamedIndividual ; rdf:type uil:UILabel ; - rdfs:label "Browse Class Group"@en-US ; + rdfs:label "Browse class group"@en-US ; uil:hasApp "Vitro" ; uil:hasKey "browse_class_group" ; uil:hasPackage "Vitro-languages" . @@ -2433,7 +2433,7 @@ uil-data:upload_photo.Vitro uil-data:sparql_query_results.Vitro rdf:type owl:NamedIndividual ; rdf:type uil:UILabel ; - rdfs:label "Sparql Query Results"@en-US ; + rdfs:label "SPARQL query results"@en-US ; uil:hasApp "Vitro" ; uil:hasKey "sparql_query_results" ; uil:hasPackage "Vitro-languages" . @@ -6871,6 +6871,29 @@ uil-data:reporting_config_dataSource_outputName.Vitro uil:hasApp "Vitro" ; uil:hasKey "reporting_config_dataSource_outputName" . +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"@en-US ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Browse search filter results"@en-US ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Select search filter"@en-US ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . uil-data:user_roles.Vitro rdf:type owl:NamedIndividual ; @@ -6899,3 +6922,12 @@ uil-data:remove_role_confirmation.Vitro rdfs:label "Are you sure you would like to remove the role?"@en-US ; uil:hasApp "Vitro" ; uil:hasKey "remove_role_confirmation" . + +uil-data:there_are_no_results_to_display.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "There are no results to display."@en-US ; + uil:hasApp "Vitro" ; + uil:hasKey "there_are_no_results_to_display" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/vitro_UiLabel.ttl index 500505dc74..37ba274c20 100644 --- a/home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,28 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,ñ,o,p,q,r,s,t,u,v,w,x,y,z"@es ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Explorar resultados de búsqueda filtrada"@es ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Seleccionar filtro de búsqueda"@es ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/fr_CA/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/fr_CA/interface-i18n/firsttime/vitro_UiLabel.ttl index 28de8e4039..ce3a9c614e 100644 --- a/home/src/main/resources/rdf/i18n/fr_CA/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/fr_CA/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,28 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"@fr-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Parcourir les résultats de filtrage de recherche"@fr-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Sélectionner un filtre de recherche"@fr-CA ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/pt_BR/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/pt_BR/interface-i18n/firsttime/vitro_UiLabel.ttl index 5626b500ad..37f6459c41 100644 --- a/home/src/main/resources/rdf/i18n/pt_BR/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/pt_BR/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,28 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"@pt-BR ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Navegar pelos resultados de filtragem de pesquisa"@pt-BR ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Selecionar filtro de pesquisa"@pt-BR ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + diff --git a/home/src/main/resources/rdf/i18n/ru_RU/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/ru_RU/interface-i18n/firsttime/vitro_UiLabel.ttl index 6f309705c7..f2d44ed8fa 100644 --- a/home/src/main/resources/rdf/i18n/ru_RU/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/ru_RU/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,29 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "а,б,в,г,д,е,ё,ж,з,и,ӣ,к,л,м,н,о,п,р,с,т,у,ф,х,ц,ч,ш,щ,э,ю,я"@ru-RU ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Отображение результатов поискового фильтра"@ru-RU ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Выберите фильтр"@ru-RU ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . + + diff --git a/home/src/main/resources/rdf/i18n/sr_Latn_RS/interface-i18n/firsttime/vitro_UiLabel.ttl b/home/src/main/resources/rdf/i18n/sr_Latn_RS/interface-i18n/firsttime/vitro_UiLabel.ttl index 343ce840bc..3b1d77d3dd 100644 --- a/home/src/main/resources/rdf/i18n/sr_Latn_RS/interface-i18n/firsttime/vitro_UiLabel.ttl +++ b/home/src/main/resources/rdf/i18n/sr_Latn_RS/interface-i18n/firsttime/vitro_UiLabel.ttl @@ -6429,3 +6429,27 @@ uil-data:password_reset_admin_notification_email_html.Vitro uil:hasApp "Vitro" ; uil:hasKey "password_reset_admin_notification_email_html" ; uil:hasPackage "Vitro-languages" . + +uil-data:browse_results_alphabetical_index.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "a,b,c,č,ć,d,dž,đ,e,f,g,h,i,j,k,l,lj,m,n,nj,o,p,r,s,š,t,u,v,z,ž"@sr-Latn-RS ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_results_alphabetical_index" ; + uil:hasPackage "Vitro-languages" . + +uil-data:browse_search_filter_facets.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Pregledajte rezultate filtriranja pretrage"@sr-Latn-RS ; + uil:hasApp "Vitro" ; + uil:hasKey "browse_search_filter_facets" ; + uil:hasPackage "Vitro-languages" . + +uil-data:select_search_filter_to_browse.Vitro + rdf:type owl:NamedIndividual ; + rdf:type uil:UILabel ; + rdfs:label "Izaberite filter za pretragu"@sr-Latn-RS ; + uil:hasApp "Vitro" ; + uil:hasKey "select_search_filter_to_browse" ; + uil:hasPackage "Vitro-languages" . diff --git a/home/src/main/resources/rdf/tbox/filegraph/search_ontology.n3 b/home/src/main/resources/rdf/tbox/filegraph/search_ontology.n3 index d232e77ee7..e7ddc985af 100644 --- a/home/src/main/resources/rdf/tbox/filegraph/search_ontology.n3 +++ b/home/src/main/resources/rdf/tbox/filegraph/search_ontology.n3 @@ -41,6 +41,10 @@ search:PublicParameter a owl:Class . search:SearchField a owl:Class . +search:SortDirection a owl:Class . + +search:SortingObjectType a owl:Class . + search:FilterValue a owl:Class ; rdfs:subClassOf search:PublicParameter . @@ -60,10 +64,6 @@ search:multivalued a owl:DatatypeProperty , owl:FunctionalPrope rdfs:domain search:SearchField ; rdfs:range xsd:boolean . -search:isAscending a owl:DatatypeProperty , owl:FunctionalProperty ; - rdfs:domain search:Sort ; - rdfs:range xsd:boolean . - search:indexField a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:domain search:SearchField ; rdfs:range xsd:string . @@ -81,6 +81,15 @@ search:isLanguageSpecific rdfs:domain search:SearchField ; rdfs:range xsd:boolean . +search:direction a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:domain [ rdf:type owl:Class ; + owl:unionOf ( + search:Sort + search:Filter + ) + ] ; + rdfs:range search:SortDirection . + search:filterField a owl:ObjectProperty , owl:FunctionalProperty ; rdfs:domain search:Filter ; rdfs:range search:SearchField . @@ -89,7 +98,7 @@ search:isUriValues a owl:DatatypeProperty , owl:FunctionalPrope rdfs:domain search:Filter ; rdfs:range xsd:boolean . -search:order a owl:FunctionalProperty , owl:DatatypeProperty ; +search:rank a owl:FunctionalProperty , owl:DatatypeProperty ; rdfs:domain search:PublicParameter ; rdfs:range xsd:integer . @@ -106,6 +115,11 @@ search:userInputRegex rdfs:domain search:Filter ; rdfs:range xsd:boolean . +search:regexPattern + a owl:DatatypeProperty , owl:FunctionalProperty ; + rdfs:domain search:Filter ; + rdfs:range xsd:string . + search:sortField a owl:FunctionalProperty , owl:ObjectProperty ; rdfs:domain search:Sort ; rdfs:range search:SearchField . @@ -126,9 +140,13 @@ search:to a owl:DatatypeProperty , owl:FunctionalPrope rdfs:domain search:RangeFilter ; rdfs:range xsd:string . -search:hasKnownValue a owl:ObjectProperty ; - rdfs:domain search:Filter ; - rdfs:range search:FilterValue . +search:hasKnownValue a owl:ObjectProperty ; + rdfs:domain search:Filter ; + rdfs:range search:FilterValue . + +search:sortValuesBy a owl:ObjectProperty, owl:FunctionalProperty ; + rdfs:domain search:Filter ; + rdfs:range search:SortingObjectType . search:public a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:domain search:PublicParameter ; @@ -138,13 +156,29 @@ search:display a owl:DatatypeProperty , owl:FunctionalPrope rdfs:domain search:Sort ; rdfs:range xsd:boolean . +search:reverseFacetOrder a owl:DatatypeProperty , owl:FunctionalProperty ; + rdfs:domain search:Filter ; + rdfs:range xsd:boolean . + + search:isDefaultForRole a ; rdfs:domain ; rdfs:range auth:PermissionSet . +search:limitDisplayTo + a ; + rdfs:domain [ rdf:type owl:Class ; + owl:unionOf ( search:Sort + search:Filter + search:FilterGroup + search:FilterValue + ) + ] ; + rdfs:range auth:PermissionSet . + search:moreLimit a , ; rdfs:domain ; rdfs:range ; - rdfs:subPropertyOf . + . diff --git a/webapp/src/main/webapp/css/search-results.css b/webapp/src/main/webapp/css/search-results.css index 235955e29a..ffba8fe2c3 100644 --- a/webapp/src/main/webapp/css/search-results.css +++ b/webapp/src/main/webapp/css/search-results.css @@ -41,10 +41,44 @@ display: block; } -.tab-pane > label { +.tab > label { display: inline-block; } +.tab > a:not(.more-facets-link):not(.less-facets-link){ + padding: 10px 15px; + position: relative; + display: block; + float: left; + text-decoration: none; + color: #555; +} + +.tab { + display: block; +} + +.tabs { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1px; +} + +#filter-groups > .tab { + border: 1px solid #ddd; + border-bottom: 0; + border-radius: 4px 4px 0 0; +} +#filter-groups { + border-bottom: 1px solid #ddd; +} + +#filter-groups > .tab.active { + margin-bottom: -1px; + background-color: #fff; +} + input[type=radio]:checked + label { background-color: #0F6568; padding: 2px 5px 2px 5px; @@ -101,10 +135,18 @@ input[type=checkbox]:not(:checked) + label { padding: 0.3em; } +.filter-area >.tab.active { + width: 100%; +} + .range-filter { padding: 15px; } +.search-filter-group-container { + border-bottom: solid 1px #ddd; + width: 100%; +} #search-filter-container > ul > li > a { padding: 7px 7px; @@ -213,5 +255,15 @@ a.hidden-search-option { } .extended-search-input-group { - display: none; + display: none; } + +#search-form-footer select { + border: 1px solid #e0dfdf; + margin-left: .5em; + height : 3em; +} + +.facet-input { + width: 100%; +} \ No newline at end of file diff --git a/webapp/src/main/webapp/css/search.css b/webapp/src/main/webapp/css/search.css index ec0639e9ab..249bcddece 100644 --- a/webapp/src/main/webapp/css/search.css +++ b/webapp/src/main/webapp/css/search.css @@ -80,6 +80,10 @@ span#searchHelp { font-size:.825em; padding-right:32px } + +.tab a:hover { + text-decoration: none; +} img#downloadIcon { cursor: pointer; margin-left: 6px; diff --git a/webapp/src/main/webapp/css/search/custom_filters.css b/webapp/src/main/webapp/css/search/custom_filters.css new file mode 100644 index 0000000000..41d37e1c27 --- /dev/null +++ b/webapp/src/main/webapp/css/search/custom_filters.css @@ -0,0 +1,309 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ +.li-selected>a>label::before { + content: url(../../images/checkMark.svg); + color: #2ea0cf; + padding-right: 5px; + margin-left: -17px; +} + +.checked-search-input-label { + padding: 2px 5px 2px 5px; + border: 0px; + background-color: inherit; +} + +.range-slider { + margin-bottom: 15px; +} + +.range-slider-end { + display: inline-block; +} + +.range-slider-start { + display: inline-block; +} + +#filter-form-sort { + margin: 0px; + border-bottom: 0px; + border-top: 0px; + border-right: 0px; + height: 3em; + border-radius: unset; + color: rgb(112, 106, 102); + font-family: inherit; +} + +#filter-form-hits-per-page { + margin: 0px; + border-bottom: 0px; + border-top: 0px; + border-right: 0px; + height: 3em; + border-radius: unset; + color: rgb(112, 106, 102); + font-family: inherit; +} + +#browsing-options { + border-bottom: 1px solid #dde4e3; + padding-left: 11px; + display: flex; + justify-content: space-between; +} + +.display-title { + font-size: small; + display: block; +} + +.pagination-container { + width: 100%; + padding: 20px 20px; + display: inline-block; + font-size: .8em; + box-sizing: border-box; +} + +.pagination-container:not(:last-child) { + border-bottom: 1px solid #dde4e3; +} + +.pagination-container ul { + display: inline; +} + +.pagination-container li.selected { + padding: 0 0.4em; + line-height: 1.5; + color: #333; + background: #ddd; +} + +.pagination-container li { + display: inline-block; + border: none; +} + +.pagination-container li a { + padding: 0 0.4em; +} + +.pagination-container .round { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +#browse-filters { + width: 271px; + float: left; + background-color: #f1f2ee; +} + +#browse-filters>li:not(:last-child) { + border-bottom: 1px dotted #dde4e3; +} + +#browse-filters>li { + margin-left: 12px; + margin-right: 10px; + padding-top: 10px; + padding-bottom: 10px; + list-style: none; +} + +#browse-filters li a { + padding-left: 10px; + text-decoration: none; + display: block; +} + +#browse-filters li a label { + display: block; + margin: 0px; +} + +#browse-filters li a label:hover { + color: #2ea0cf; +} + +.li-selected a label { + color: #2ea0cf; +} + +li>a>label:hover { + color: #2ea0cf; +} + +#browse-filters li:not(.li-selected)>a>label:hover::before { + content: url(../../images/checkMark.svg); + padding-right: 5px; + margin-left: -17px; +} + +#browse-filters li.li-selected>a>label:hover::before { + content: unset; + padding-right: unset; + margin-left: unset; +} + +.facet-values { + padding-left: 10px; +} + +.facet-values li { + padding-top: 10px; +} + +.facet-values label { + cursor: pointer; +} + +.filter-tab>a:focus { + color: unset; + text-decoration: none; +} + +.range-filter { + padding-right: 16px; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 4px; +} + +.download-url { + padding: 5px 25px 5px; +} + +#downloadIcon { + padding-left: 7px; + font-size: 14px; + color: rgb(112, 106, 102); + font-family: inherit; +} + +.facet-input { + width: 100%; +} + +.hidden-search-option { + display: none; +} + +.hidden-all-search-options { + display: none; +} + +.download-results-text-button { + border: 0px; + background-color: transparent; +} + +nav#alphabetical-index-container { + border-bottom: 1px solid #dde4e3; + border-right: 1px solid #dde4e3; +} + +nav#alphabetical-index-container { + width: 100%; + float: left; + padding-left: 10px; + margin-left: 1px; + box-sizing: border-box; +} + +ul#alphabetical-index-individuals { + background-color: #fff; + float: left; +} + +ul#alphabetical-index-individuals li { + float: left; + margin-right: 5px; + font-size: .8em; +} + +#individuals-in-class ul { + list-style: none; + float: left; + width: 100%; +} + +#individuals-in-class { + float: right; + width: 68%; + padding-bottom: 30px; + margin-bottom: 30px; + margin-right: 17px; + min-height: 170px; +} + +#individuals-in-class span.title { + display: block; + margin: .8em 0 0 0; + font-size: .8em; + line-height: 1; +} + +ul#alphabetical-index-individuals a:hover, ul#alphabetical-index-individuals a.selected + { + background: url(../../images/arrowSmall.svg) 0px 6px no-repeat; + color: #2ea0cf; +} + +ul#alphabetical-index-individuals a { + display: block; + height: 35px; + margin-left: 0; + padding-left: 8px; + text-decoration: none; +} + +#browse-by { + clear: both; + width: 90%; + margin: 0 auto; + overflow: hidden; +} + +#browse-by h2 { + height: 44px; + line-height: 44px; + padding-left: 15px; + margin-bottom: 27px; +} + +li.individual { + width: 90%; + padding: 20px 0; + margin-left: 20px; + margin-right: 30px; + overflow: hidden; +} + +li.individual img { + float: left; + margin-right: 20px; +} + +li.individual h1 { + padding: 0; + line-height: 1.2em; + font-weight: bold; +} + +li.individual h1.thumb { + padding-top: 30px; +} + +p.no-individuals:first-child { + margin-top: 2em; + margin-left: 2em; + margin-bottom: 1em; +} + +p.no-individuals:not(:first-child) { + margin-left: 2em; +} \ No newline at end of file diff --git a/webapp/src/main/webapp/images/arrowSmall.svg b/webapp/src/main/webapp/images/arrowSmall.svg new file mode 100644 index 0000000000..93f6243eb9 --- /dev/null +++ b/webapp/src/main/webapp/images/arrowSmall.svg @@ -0,0 +1,125 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/src/main/webapp/images/checkMark.svg b/webapp/src/main/webapp/images/checkMark.svg new file mode 100644 index 0000000000..f2f84510c3 --- /dev/null +++ b/webapp/src/main/webapp/images/checkMark.svg @@ -0,0 +1,124 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/src/main/webapp/js/menupage/pageManagementUtils.js b/webapp/src/main/webapp/js/menupage/pageManagementUtils.js index abab7f5753..95c9ab503d 100644 --- a/webapp/src/main/webapp/js/menupage/pageManagementUtils.js +++ b/webapp/src/main/webapp/js/menupage/pageManagementUtils.js @@ -102,6 +102,7 @@ var pageManagementUtils = { //list of options this.contentTypeSelectOptions = $('select#typeSelect option'); this.classGroupSection = $("section#browseClassGroup"); + this.searchFilterSection = $("section#searchFilterValues"); this.sparqlQuerySection = $("section#sparqlQuery"); this.fixedHTMLSection = $("section#fixedHtml"); this.searchIndividualsSection = $("section#searchIndividuals"); @@ -145,6 +146,7 @@ var pageManagementUtils = { // $("section#pageDetails").hide(); this.headerBar.hide(); this.classGroupSection.hide(); + this.searchFilterSection.hide(); this.sparqlQuerySection.hide(); this.fixedHTMLSection.hide(); this.searchIndividualsSection.hide(); @@ -241,6 +243,7 @@ var pageManagementUtils = { pageManagementUtils.clearSourceTemplateValues(); pageManagementUtils.headerBar.hide(); pageManagementUtils.classGroupSection.hide(); + pageManagementUtils.searchFilterSection.hide(); pageManagementUtils.fixedHTMLSection.hide(); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.contentTypeSelectOptions.eq(0).prop('selected', 'selected'); @@ -287,6 +290,7 @@ var pageManagementUtils = { } //Hide all sections pageManagementUtils.classGroupSection.hide(); + pageManagementUtils.searchFilterSection.hide(); pageManagementUtils.fixedHTMLSection.hide(); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.searchIndividualsSection.hide(); @@ -359,6 +363,7 @@ var pageManagementUtils = { pageManagementUtils.clearSourceTemplateValues(); if ( _this.contentTypeSelect.val() == "browseClassGroup" ) { pageManagementUtils.classGroupSection.show(); + pageManagementUtils.searchFilterSection.hide(); pageManagementUtils.fixedHTMLSection.hide(); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.searchIndividualsSection.hide(); @@ -366,7 +371,7 @@ var pageManagementUtils = { pageManagementUtils.headerBar.show(); $('div#selfContainedDiv').hide(); } - if ( _this.contentTypeSelect.val() == "fixedHtml" || _this.contentTypeSelect.val() == "sparqlQuery" || _this.contentTypeSelect.val() == "searchIndividuals") { + if ( _this.contentTypeSelect.val() == "fixedHtml" || _this.contentTypeSelect.val() == "sparqlQuery" || _this.contentTypeSelect.val() == "searchIndividuals" || _this.contentTypeSelect.val() == "searchFilterValues") { pageManagementUtils.classGroupSection.hide(); //if fixed html show that, otherwise show sparql results if ( _this.contentTypeSelect.val() == "fixedHtml" ) { @@ -375,17 +380,26 @@ var pageManagementUtils = { initTinyMCE.initEditor(pageManagementUtils.fixedHTMLSection.find("#fixedHTMLValue")); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.searchIndividualsSection.hide(); + pageManagementUtils.searchFilterSection.hide(); } else if (_this.contentTypeSelect.val() == "sparqlQuery"){ pageManagementUtils.headerBar.text(pageManagementUtils.sparqlResults + " - "); pageManagementUtils.sparqlQuerySection.show(); pageManagementUtils.fixedHTMLSection.hide(); pageManagementUtils.searchIndividualsSection.hide(); + pageManagementUtils.searchFilterSection.hide(); + } else if (_this.contentTypeSelect.val() == "searchFilterValues"){ + pageManagementUtils.headerBar.text(pageManagementUtils.browseSearchFilterValues + " - "); + pageManagementUtils.searchFilterSection.show(); + pageManagementUtils.sparqlQuerySection.hide(); + pageManagementUtils.fixedHTMLSection.hide(); + pageManagementUtils.searchIndividualsSection.hide(); } else { //search individuals pageManagementUtils.headerBar.text(pageManagementUtils.searchIndividuals + " - "); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.fixedHTMLSection.hide(); + pageManagementUtils.searchFilterSection.hide(); pageManagementUtils.searchIndividualsSection.show(); } @@ -395,6 +409,7 @@ var pageManagementUtils = { } if ( _this.contentTypeSelect.val() == "" ) { pageManagementUtils.classGroupSection.hide(); + pageManagementUtils.searchFilterSection.hide(); pageManagementUtils.fixedHTMLSection.hide(); pageManagementUtils.sparqlQuerySection.hide(); pageManagementUtils.searchIndividualsSection.hide(); diff --git a/webapp/src/main/webapp/js/menupage/processDataGetterUtils.js b/webapp/src/main/webapp/js/menupage/processDataGetterUtils.js index 6639480417..454698f715 100644 --- a/webapp/src/main/webapp/js/menupage/processDataGetterUtils.js +++ b/webapp/src/main/webapp/js/menupage/processDataGetterUtils.js @@ -7,6 +7,7 @@ //This will need to be overridden or extended, what have you.. in VIVO var processDataGetterUtils = { dataGetterProcessorMap:{"browseClassGroup": processClassGroupDataGetterContent, + "searchFilterValues": processSearchFilterValuesDataGetterContent, "sparqlQuery": processSparqlDataGetterContent, "fixedHtml":processFixedHTMLDataGetterContent, "individualsForClasses":processIndividualsForClassesDataGetterContent, diff --git a/webapp/src/main/webapp/js/menupage/processSearchFilterValuesDataGetterContent.js b/webapp/src/main/webapp/js/menupage/processSearchFilterValuesDataGetterContent.js new file mode 100644 index 0000000000..55e1855249 --- /dev/null +++ b/webapp/src/main/webapp/js/menupage/processSearchFilterValuesDataGetterContent.js @@ -0,0 +1,44 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ + +$.extend(this, i18nStringsBrowseSearchFilters); +//Process sparql data getter and provide a json object with the necessary information +//Depending on what is included here, a different type of data getter might be used +var processSearchFilterValuesDataGetterContent = { + dataGetterClass:null, + //can use this if expect to initialize from elsewhere + initProcessor:function(dataGetterClassInput) { + this.dataGetterClass = dataGetterClassInput; + }, + + processPageContentSection:function(pageContentSection) { + var searchFilter = pageContentSection.find("select[name='selectSearchFilter']").val(); + //query model should also be an input, ensure class group URI is saved as URI and not string + var returnObject = {filterUri:searchFilter, dataGetterClass:this.dataGetterClass}; + return returnObject; + }, + //For an existing set of content where form is already set, fill in the values + populatePageContentSection:function(existingContentObject, pageContentSection) { + let searchFilterValue = existingContentObject["searchFilterUri"]; + pageContentSection.find("select[name='selectSearchFilter']").val(searchFilterValue); + }, + + retrieveContentLabel:function() { + return i18nStringsBrowseSearchFilters.browseSearchFilter; + }, + retrieveAdditionalLabelText:function(existingContentObject) { + var label = ""; + var filterName = existingContentObject["searchFilterName"]; + if(filterName != null) { + label = filterName; + } + return label; + }, + //Validation on form submit: Check to see that filter has been selected + validateFormSubmission: function(pageContentSection, pageContentSectionLabel) { + var validationError = ""; + if (pageContentSection.find('select[name="selectSearchFilter"]').val() =='-1') { + validationError += pageContentSectionLabel + ": " + i18nStringsBrowseSearchFilters.supplySearchFilterValues + "
"; + } + return validationError; + } +} diff --git a/webapp/src/main/webapp/js/search/search_results.js b/webapp/src/main/webapp/js/search/search_results.js new file mode 100644 index 0000000000..63d1409d90 --- /dev/null +++ b/webapp/src/main/webapp/js/search/search_results.js @@ -0,0 +1,128 @@ +$("input:radio").on("click",function (e) { + var input=$(this); + if (input.is(".selected-input")) { + input.prop("checked",false); + } else { + input.prop("checked",true); + } + $('#' + searchFormId).submit(); +}); + +$("input:checkbox").on("click",function (e) { + var input=$(this); + input.checked = !input.checked; + $('#' + searchFormId).submit(); +}); + +function clearInput(event, elementId) { + let inputElements = document.querySelectorAll('input[id='+ elementId + ']'); + if (inputElements.length == 0){ + inputElements = document.querySelectorAll('input[name=' + elementId + ']'); + if (inputElements.length == 0){ + return; + } + } + let inputEl = inputElements[0]; + inputEl.value = ""; + event.target.classList.add("unchecked-selected-search-input-label"); + $('#' + searchFormId).submit(); +} + +function createSliders(){ + sliders = document.getElementsByClassName('range-slider-container'); + for (let sliderElement of sliders) { + createSlider(sliderElement); + } + $(".noUi-handle").on("mousedown", function (e) { + $(this)[0].setPointerCapture(e.pointerId); + }); + $(".noUi-handle").on("mouseup", function (e) { + $('#' + searchFormId).submit(); + }); + $(".noUi-handle").on("pointerup", function (e) { + $('#' + searchFormId).submit(); + }); +}; + +function createSlider(sliderContainer){ + rangeSlider = sliderContainer.querySelector('.range-slider'); + + noUiSlider.create(rangeSlider, { + range: { + min: Number(sliderContainer.getAttribute('min')), + max: Number(sliderContainer.getAttribute('max')) + }, + + step: 1, + start: [Number(sliderContainer.querySelector('.range-slider-start').textContent), + Number(sliderContainer.querySelector('.range-slider-end').textContent)], + + format: wNumb({ + decimals: 0 + }) + }); + + var dateValues = [ + sliderContainer.querySelector('.range-slider-start'), + sliderContainer.querySelector('.range-slider-end') + ]; + + var input = sliderContainer.querySelector('.range-slider-input'); + var first = true; + + rangeSlider.noUiSlider.on('update', function (values, handle) { + dateValues[handle].innerHTML = values[handle]; + var active = input.getAttribute('active'); + if (active === null){ + input.setAttribute('active', "false"); + } else if (active !== "true"){ + input.setAttribute('active', "true"); + } else { + var startDate = new Date(+values[0],0,1); + var endDate = new Date(+values[1],0,1); + input.value = startDate.toISOString() + " " + endDate.toISOString(); + } + }); +} + +window.onload = (event) => { + createSliders(); +}; + +$('#' + searchFormId).submit(function () { +$('#' + searchFormId) + .find('input') + .filter(function () { + return !this.value; + }) + .prop('name', ''); +}); + +function expandSearchOptions(){ + $(event.target).parent().children('.additional-search-options').removeClass("hidden-search-option"); + $(event.target).parent().children('.less-facets-link').show(); + $(event.target).hide(); +} + +function collapseSearchOptions(){ + $(event.target).parent().children('.additional-search-options').addClass("hidden-search-option"); + $(event.target).parent().children('.more-facets-link').show(); + $(event.target).hide(); +} + +function openTab(event, tabName) { + let currentTab = document.getElementById(tabName); + let tabs = currentTab.parentElement.querySelectorAll(':scope > .tab'); + for (let i = 0; i < tabs.length; i++) { + let tab = tabs[i]; + tab.classList.add('fade'); + } + let tabElement = event.srcElement.parentElement; + let srcTabs = tabElement.parentElement.querySelectorAll(':scope > .tab'); + for (let i = 0; i < srcTabs.length; i++) { + let tab = srcTabs[i]; + tab.classList.remove('active'); + } + tabElement.classList.add('active'); + currentTab.classList.remove('fade'); +} \ No newline at end of file diff --git a/webapp/src/main/webapp/js/searchDownload.js b/webapp/src/main/webapp/js/searchDownload.js index 189ac9c5d2..ba185c131a 100644 --- a/webapp/src/main/webapp/js/searchDownload.js +++ b/webapp/src/main/webapp/js/searchDownload.js @@ -18,7 +18,7 @@ $(document).ready(function(){ $( "#amount" ).val( $( "#slider-vertical" ).slider( "value" ) ); } - setTooltip("img#downloadIcon", { + setTooltip("#downloadIcon", { title: '
' +'

' +'

' diff --git a/webapp/src/main/webapp/templates/freemarker/body/menupage/browseSearchFilterValues.ftl b/webapp/src/main/webapp/templates/freemarker/body/menupage/browseSearchFilterValues.ftl new file mode 100644 index 0000000000..8fa2a7325f --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/body/menupage/browseSearchFilterValues.ftl @@ -0,0 +1,280 @@ +<#-- $This file is distributed under the terms of the license in LICENSE$ --> +<#import "search-lib.ftl" as sl> + +<#-- <#assign additionalFilters = ["type"]> --> +<#if filters[searchFilter]??> + <#if languageAware > + <#assign indexFilterName = "initial"> + <#else> + <#assign indexFilterName = "raw_initial"> + + + + +
+
+
+ +
+
+ + <@sl.showHits "filter-form" /> + <@showSortOptions /> +
+ <@alphabeticalIndexLinks indexFilterName/> + <@printPagingLinks /> + <@filteredResults indexFilterName /> + <@printPagingLinks /> +
+
+
+ +
+ + + + + +<#macro filterTab filterId> + <#if filters[filterId]?? > + <#assign filter = filters[filterId] > + <#if filter.displayed> + <#assign filterValues><@getValues filter /> + <#if filterValues?has_content> +
  • + ${filter.name?html} + ${filterValues} +
  • + + + + + +<#macro getValues filter> + <#if filter.type == "RangeFilter"> + + <#else> + <#assign facets><@filterFacets filter /> + <#if facets?has_content> +
      + ${facets} +
    + + + + +<#macro filteredResults indexFilterName> +
      + <#if individuals?has_content> + <#list individuals as individual> + <@shortView uri=individual.uri viewContext="browse" /> + + <#elseif filters[indexFilterName]?? && filters[indexFilterName].inputText?has_content> + <#assign selectedLetter = filters[indexFilterName].inputText > +
    • +

      ${i18n().there_are_no_entries_starting_with} ${selectedLetter?upper_case}.

      +

      ${i18n().try_another_letter}

      +
    • + <#else> +
    • ${i18n().there_are_no_results_to_display}

    • + +
    + + +<#macro filterFacets filter > + <#if ( !filter.localizationRequired && filter.values?values?size > filter.moreLimit) > +
  • + <@sl.createAutocomplete filter "filter-form" /> +
  • + + <#assign idCounter = 1 > + <#assign notSelectedCount = 0> + <#assign additionalLabels = false> + <#list filter.values?values as value> + <#if !value.displayed> + <#continue> + + <#assign valueLabel = value.name > + <#assign resultsCount = value.count > + <#if !(valueLabel?has_content)> + <#assign valueLabel = value.id > + + <#if value.selected> + <#if value.id != "[* TO *]"> +
  • + + <@sl.getInput filter value sl.getValueID(filter.id, idCounter) idCounter 'filter-form' /> + <@sl.getLabel sl.getValueID(filter.id, idCounter)?html value filter resultsCount /> + +
  • + + <#else> + <#if resultsCount != 0> + <#if filter.moreLimit = notSelectedCount > + <#assign additionalLabels = true> + + +
  • class="additional-search-options hidden-search-option" > + + <@sl.getInput filter value sl.getValueID(filter.id, idCounter) idCounter 'filter-form' /> + <@sl.getLabel sl.getValueID(filter.id, idCounter) value filter resultsCount /> + +
  • + <#assign notSelectedCount += 1> + + + <#assign idCounter = idCounter + 1> + + <#if additionalLabels > + + + + +<#macro alphabeticalIndexLinks indexFilterName> + <#if filters[indexFilterName]??> + <#assign indexFilter = filters[indexFilterName]> + + + + +<#macro printPagingLinks> + <#if (pagingLinks?? && pagingLinks?size > 0)> +
    + ${i18n().pages}: +
      + <#list pagingLinks as link> + <#if link.url??> +
    • ${link.text?html}
    • + <#else> +
    • ${link.text?html}
    • <#-- no link if current page --> + + +
    +
    + + + +<#macro getAlphabetLabel valueId label> + + +<#-- create radio input fields for alphabetical indexes --> +<#macro getAlphabetInput filter filterValue valueID form="filter-form"> + <#assign checked = ""> + <#if filter.inputText == filterValue> + <#assign checked = " checked=\"checked\" " > + <#assign class = "selected-input" > + + <#assign type = "radio" > + <#assign filterName = filter.id > + + + + +<#macro showSortOptions> + <#if !sortOptionIds??> + <#assign sortOptionIds = ["titledesc", "titleasc"]> + + <#if sortOptions?has_content && sortOptionIds?? && sortOptionIds?is_sequence> + + + + +${scripts.add('')} +${scripts.add('')} +${stylesheets.add('')} +${stylesheets.add('')} +${headScripts.add('')} +${headScripts.add('')} +${headScripts.add('')} +${headScripts.add('')} +${headScripts.add('')} +${stylesheets.add('')} + +${headScripts.add('')} + diff --git a/webapp/src/main/webapp/templates/freemarker/body/partials/shortview/view-browse-default.ftl b/webapp/src/main/webapp/templates/freemarker/body/partials/shortview/view-browse-default.ftl index 6118b2ccfa..548b1aaa0b 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/partials/shortview/view-browse-default.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/partials/shortview/view-browse-default.ftl @@ -17,7 +17,7 @@ -<#assign cleanTypes = 'edu.cornell.mannlib.vitro.webapp.web.TemplateUtils$DropFromSequence'?new()(individual.mostSpecificTypes, vclass) /> +<#assign cleanTypes = 'edu.cornell.mannlib.vitro.webapp.web.TemplateUtils$DropFromSequence'?new()(individual.mostSpecificTypes, vclass!"") /> <#if cleanTypes?size == 1> ${cleanTypes[0]} <#elseif (cleanTypes?size > 1) > diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-lib.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-lib.ftl new file mode 100644 index 0000000000..9a85fb0250 --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/body/search/search-lib.ftl @@ -0,0 +1,350 @@ + +<#macro printPagingLinks> + +<#-- Paging controls --> + <#if (pagingLinks?size > 0)> +
    + ${i18n().pages}: + <#if prevPage??> + <#list pagingLinks as link> + <#if link.url??> + ${link.text?html} + <#else> + ${link.text?html} <#-- no link if current page --> + + + <#if nextPage??> +
    + + + +<#macro printResultNumbers> +

    + <#escape x as x?html> + ${i18n().results_found(hitCount)} + + ${i18n().download_results} +

    + + +<#macro searchForm> +
    + <@printSelectedFilterValueLabels filters /> +
    + <#assign filterGroupTabsContent> + <@filterGroupTabs/> + + <#if filterGroupTabsContent?has_content> +
    + ${filterGroupTabsContent} +
    +
    + <#assign active = true> + <#list filterGroups as group> + <#if group.displayed && !isEmptyGroup(group)> + <@groupFilters group active/> + <#assign active = false> + + +
    + + + + +<#macro filterGroupTabs > + <#assign active = true> + <#list filterGroups as group> + <#if group.displayed && !isEmptyGroup(group)> + <@searchFormGroupTab group active/> + <#assign active = false> + + + + +<#macro groupFilters group active> +
    +
    +
    + <#assign assignedActive = false> + <#list group.filters as filterId> + <#if filters[filterId]??> + <#assign f = filters[filterId]> + <#if f.displayed && !isEmptyFilter(f) > + <@searchFormFilterTab f assignedActive/> + <#if !assignedActive && (f.selected || emptySearch )> + <#assign assignedActive = true> + + + + +
    +
    +
    + <#assign assignedActive = false> + <#list group.filters as filterId> + <#if filters[filterId]??> + <#assign f = filters[filterId]> + <#if f.displayed && !isEmptyFilter(f) > + <@printFilterValues f assignedActive emptySearch/> + <#if !assignedActive && ( f.selected || emptySearch )> + <#assign assignedActive = true> + + + + +
    +
    + + +<#macro printSelectedFilterValueLabels filters> + <#list filters?values as filter> + <#if filter.inputText?has_content> + <@userSelectedInput filter "search-form" /> + <#else> + <#assign valueNumber = 1> + <#list filter.values?values as v> + <#if v.selected> + <@getInput filter v getValueID(filter.id, valueNumber) valueNumber /> + <#if filter.displayed> + <@getSelectedLabel getValueID(filter.id, valueNumber)?html v filter v.count /> + + + <#assign valueNumber = valueNumber + 1> + + + + + +<#macro printSorting> + <#if sortOptions?has_content> +
    + +
    + + + +<#macro showHits form="search-form"> + + + +<#macro searchFormGroupTab group active > + + + +<#macro searchFormFilterTab filter assignedActive> + + + +<#macro printFilterValues filter assignedActive isEmptySearch> +
    + <#if filter.id == "querytext"> + <#-- skip query text filter --> + <#elseif filter.type == "RangeFilter"> + <@rangeFilter filter "search-form" /> + <#else> + <#if filter.input > +
    + <@createUserInput filter /> +
    + + <#if ( !filter.localizationRequired && filter.values?values?size > filter.moreLimit) > +
    + <@createAutocomplete filter /> +
    + + <#assign valueNumber = 1> + <#assign notSelectedCount = 0> + <#assign additionalLabels = false> + <#list filter.values?values as v> + <#if !v.selected> + <#if filter.moreLimit = notSelectedCount > + <#assign additionalLabels = true> + ${i18n().paging_link_more} + + <#if v.displayed> + <@getInput filter v getValueID(filter.id, valueNumber) valueNumber /> + <@getLabel getValueID(filter.id, valueNumber)?html v filter v.count additionalLabels /> + + <#assign notSelectedCount += 1> + + <#assign valueNumber += 1> + + <#if additionalLabels > + ${i18n().display_less} + + +
    + + +<#macro rangeFilter filter form> + <#assign min = filter.min > + <#assign max = filter.max > + <#assign from = filter.fromYear > + <#assign to = filter.toYear > + +
    +
    +
    + ${i18n().from} + <#if from?has_content> +
    ${from?html}
    + <#else> +
    ${min?html}
    + + ${i18n().to} + <#if to?has_content> +
    ${to?html}
    + <#else> +
    ${max?html}
    + + +
    +
    + + + +<#macro getSelectedLabel valueId value filter count > + <#assign label = filter.name + " : " + value.name > + <#if !filter.localizationRequired> + <#assign label = filter.name + " : " + value.id > + + + + +<#macro getLabel valueId value filter count additional=false > + <#assign label = value.name > + <#assign additionalClass = "" > + <#if !filter.localizationRequired> + <#assign label = value.id > + + <#if additional=true> + <#assign additionalClass = "additional-search-options hidden-search-option" > + + + + +<#macro userSelectedInput filter form> + <#if filter.inputText?has_content> + <#assign inputID = "filter_input_" + filter.id > + <#if filter.id == "querytext"> + <#assign inputID = filter.id > + + + + <#assign from = filter.fromYear > + <#assign to = filter.toYear > + <#if from?has_content && to?has_content > + <#assign range = i18n().from + " " + from + " " + i18n().to + " " + to > + + + + +<#macro createUserInput filter> + + + +<#macro createAutocomplete filter form="search-form"> + + + + + +<#macro getInput filter filterValue valueID valueNumber form="search-form"> + <#assign checked = "" > + <#assign class = "" > + <#assign inputName = "filters_" + valueID > + <#if filterValue.selected> + <#assign checked = " checked=\"checked\" " > + <#assign class = "selected-input" > + + <#assign type = "checkbox" > + <#if !filter.multivalued> + <#assign type = "radio" > + <#assign inputName = "filters_" + filter.id > + + <#assign filterName = filter.id > + <#if filter.multivalued> + <#assign filterName = filterName + "_" + valueNumber > + + class="${class}" > + + +<#function getValueID id number> + <#return id + "__" + number /> + + +<#function getValueLabel label count > + <#assign result = label > + <#if count!=0> + <#assign result = result + " (" + count + ")" > + + <#return result /> + + +<#function isEmptyFilter filter > + <#return filter.id == "querytext" || (filter.type != "RangeFilter" && !filter.input && filter.values?values?filter(v -> !v.selected)?size == 0 ) /> + + +<#function isEmptyGroup group > + <#list group.filters as filterId> + <#if filters[filterId]??> + <#assign f = filters[filterId]> + <#if f.displayed && !isEmptyFilter(f) > + <#return false /> + + + + <#return true /> + diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl index 5ba361e616..94294432ff 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl @@ -7,11 +7,20 @@ ${headScripts.add('')} ${headScripts.add('')} -${stylesheets.add('')} -${stylesheets.add('')} ${headScripts.add('')} ${headScripts.add('')} -${headScripts.add('')} + +<#include "search-lib.ftl"> + + <@searchForm /> @@ -22,10 +31,11 @@ ${headScripts.add(' - ${i18n().download_results} - <#-- --> - +

    + <#escape x as x?html> + ${i18n().results_found(hitCount)} + + ${i18n().download_results} +

    <#macro searchForm> -
    -
    - <@printSelectedFilterValueLabels filters /> -
    -
    - - <#assign active = true> - <#list filterGroups as group> - <#if ( user.loggedIn || group.public ) && !group.hidden > - <@groupFilters group active/> - <#assign active = false> - - -
    - -
    +
    + <@printSelectedFilterValueLabels filters /> +
    +
    + + <#assign active = true> + <#list filterGroups as group> + <#if ( user.loggedIn || group.public ) && !group.hidden > + <@groupFilters group active/> + <#assign active = false> + + +
    + <#macro groupFilters group active> - <#if active > -
    - <#else> -
    - -
    - -
    -
    - <#assign assignedActive = false> - <#list group.filters as filterId> - <#if filters[filterId]??> - <#assign f = filters[filterId]> - <#if ( user.loggedIn || f.public ) && !f.hidden > - <@printFilterValues f assignedActive emptySearch/> - <#if !assignedActive && ( f.selected || emptySearch )> - <#assign assignedActive = true> - - - - -
    -
    + <#if active > +
    + <#else> +
    + +
    + +
    +
    + <#assign assignedActive = false> + <#list group.filters as filterId> + <#if filters[filterId]??> + <#assign f = filters[filterId]> + <#if ( user.loggedIn || f.public ) && !f.hidden > + <@printFilterValues f assignedActive emptySearch/> + <#if !assignedActive && ( f.selected || emptySearch )> + <#assign assignedActive = true> + + + + +
    +
    <#macro printSelectedFilterValueLabels filters> - <#list filters?values as filter> - <#assign valueNumber = 1> - <#list filter.values?values as v> - <#if v.selected> - ${getInput(filter, v, getValueID(filter.id, valueNumber), valueNumber)} - <#if user.loggedIn || filter.public> - ${getSelectedLabel(valueNumber, v, filter)} - - - <#assign valueNumber = valueNumber + 1> - - <@userSelectedInput filter /> - + <#list filters?values as filter> + <#assign valueNumber = 1> + <#list filter.values?values as v> + <#if v.selected> + <@getInput filter v getValueID(filter.id, valueNumber) valueNumber /> + <#if user.loggedIn || filter.public> + <@getSelectedLabel valueNumber, v, filter /> + + + <#assign valueNumber = valueNumber + 1> + + <@userSelectedInput filter /> + <#macro printSorting> - <#if sorting?has_content> -
    - -
    - + <#if sorting?has_content> +
    + +
    + <#macro printHits> -
    - -
    +
    + +
    <#macro searchFormGroupTab group active > - <#if active> -
  • - <#else> -
  • - - ${group.label?html} -
  • + <#if active> +
  • + <#else> +
  • + + ${group.label?html} +
  • -<#macro searchFormFilterTab filter assignedActive isEmptySearch> - <#if filter.id == "querytext"> - <#-- skip query text filter --> - <#return> - -
  • - ${filter.name?html} -
  • +<#macro searchFormFilterTab filter assignedActive> + <#if filter.id == "querytext"> + <#-- skip query text filter --> + <#return> + +
  • + ${filter.name?html} +
  • <#macro printFilterValues filter assignedActive isEmptySearch> -
    - <#if filter.id == "querytext"> - <#-- skip query text filter --> - <#elseif filter.type == "RangeFilter"> - <@rangeFilter filter/> - <#else> - <#if filter.input> -
    - <@createUserInput filter /> -
    - - - <#assign valueNumber = 1> - <#assign additionalLabels = false> - <#list filter.values?values as v> - <#if !v.selected> - <#if filter.moreLimit = valueNumber - 1 > - <#assign additionalLabels = true> - ${i18n().paging_link_more} - - <#if user.loggedIn || v.publiclyAvailable> - ${getInput(filter, v, getValueID(filter.id, valueNumber), valueNumber)} - ${getLabel(valueNumber, v, filter, additionalLabels)} - - - <#assign valueNumber = valueNumber + 1> - - - <#if additionalLabels > - ${i18n().display_less} - - -
    +
    + <#if filter.id == "querytext"> + <#-- skip query text filter --> + <#elseif filter.type == "RangeFilter"> + <@rangeFilter filter/> + <#else> + <#if filter.input> +
    + <@createUserInput filter /> +
    + + <#assign valueNumber = 1> + <#assign additionalLabels = false> + <#list filter.values?values as v> + <#if !v.selected> + <#if filter.moreLimit = valueNumber - 1 > + <#assign additionalLabels = true> + ${i18n().paging_link_more} + + <#if user.loggedIn || v.publiclyAvailable> + <@getInput filter v getValueID(filter.id, valueNumber) valueNumber /> + <@getLabel valueNumber, v, filter, additionalLabels /> + + + <#assign valueNumber = valueNumber + 1> + + <#if additionalLabels > + ${i18n().display_less} + + +
    <#macro rangeFilter filter> - <#assign min = filter.min > - <#assign max = filter.max > - <#assign from = filter.fromYear > - <#assign to = filter.toYear > - -
    -
    -
    - ${i18n().from} - <#if from?has_content> -
    ${from?html}
    - <#else> -
    ${min?html}
    - - ${i18n().to} - <#if to?has_content> -
    ${to?html}
    - <#else> -
    ${max?html}
    - - -
    -
    + <#assign min = filter.min > + <#assign max = filter.max > + <#assign from = filter.fromYear > + <#assign to = filter.toYear > + +
    +
    +
    + ${i18n().from} + <#if from?has_content> +
    ${from?html}
    + <#else> +
    ${min?html}
    + + ${i18n().to} + <#if to?has_content> +
    ${to?html}
    + <#else> +
    ${max?html}
    + + +
    +
    -<#function getSelectedLabel valueID value filter > - <#assign label = filter.name + " : " + value.name > - <#if !filter.localizationRequired> - <#assign label = filter.name + " : " + value.id > - - <#return "" /> - +<#macro getSelectedLabel valueID value filter > + <#assign label = filter.name + " : " + value.name > + <#if !filter.localizationRequired> + <#assign label = filter.name + " : " + value.id > + + + -<#function getLabel valueID value filter additional=false > - <#assign label = value.name > - <#assign additionalClass = "" > - <#if !filter.localizationRequired> - <#assign label = value.id > - - <#if additional=true> - <#assign additionalClass = "additional-search-options hidden-search-option" > - - <#return "" /> - +<#macro getLabel valueID value filter additional=false > + <#assign label = value.name > + <#assign additionalClass = "" > + <#if !filter.localizationRequired> + <#assign label = value.id > + + <#if additional=true> + <#assign additionalClass = "additional-search-options hidden-search-option" > + + + + <#macro userSelectedInput filter> - <#if filter.inputText?has_content> - - - <#assign from = filter.fromYear > - <#assign to = filter.toYear > - <#if from?has_content && to?has_content > - <#assign range = i18n().from + " " + from + " " + i18n().to + " " + to > - - + <#if filter.inputText?has_content> + + + <#assign from = filter.fromYear > + <#assign to = filter.toYear > + <#if from?has_content && to?has_content > + <#assign range = i18n().from + " " + from + " " + i18n().to + " " + to > + + <#macro createUserInput filter> - + -<#function getInput filter filterValue valueID valueNumber> - <#assign checked = "" > - <#assign class = "" > - <#if filterValue.selected> - <#assign checked = " checked=\"checked\" " > - <#assign class = "selected-input" > - - <#assign type = "checkbox" > - <#if !filter.multivalued> - <#assign type = "radio" > - - <#assign filterName = filter.id > - <#if filter.multivalued> - <#assign filterName = filterName + "_" + valueNumber > - - - <#return "" /> - +<#macro getInput filter filterValue valueID valueNumber form="search-form"> + <#assign checked = "" > + <#assign class = "" > + <#if filterValue.selected> + <#assign checked = " checked=\"checked\" " > + <#assign class = "selected-input" > + + <#assign type = "checkbox" > + <#if !filter.multivalued> + <#assign type = "radio" > + + <#assign filterName = filter.id > + <#if filter.multivalued> + <#assign filterName = filterName + "_" + valueNumber > + + + + <#function getValueID id number> - <#return id + "__" + number /> + <#return id + "__" + number /> <#function getValueLabel label count > - <#assign result = label > - ${label} - <#if count!=0> - <#assign result = result + " (" + count + ")" > - - <#return result /> + <#assign result = label > + ${label} + <#if count!=0> + <#assign result = result + " (" + count + ")" > + + <#return result /> - ${stylesheets.add('', '')} @@ -467,3 +362,5 @@ ${headScripts.add('')} +${scripts.add('')} + diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseSearchFilterValues.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseSearchFilterValues.ftl new file mode 100644 index 0000000000..bdf3065d89 --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseSearchFilterValues.ftl @@ -0,0 +1,41 @@ +<#-- $This file is distributed under the terms of the license in LICENSE$ --> +<#--Browse Search Filter Values Section--> +<#-----------Variable assignment--------------> +<#--Requires Menu action be defined in parent template--> + +<#assign searchFilter = pageData.searchFilter /> +<#assign searchFilters = pageData.searchFilters /> +<#-- some additional processing here which shows or hides the class group selection and classes based on initial action--> +<#assign selectFilterStyle = 'class="hidden"' /> +<#-- Reveal the class group and hide the class selects if adding a new menu item or editing an existing menu item with an empty class group (no classes)--> +<#-- Menu action needs to be sent from main template--> +<#if menuAction == "Add" || !searchFilter?has_content> + <#assign selectFilterStyle = " " /> + + +<#--HTML Portion--> +
    +
    + + + +
    + + <#if menuAction == "Add"> + ${i18n().or} ${i18n().cancel_link} + +
    + + <#--Include JavaScript specific to the types of data getters related to this content--> +${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--contentTemplates.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--contentTemplates.ftl index 891497e462..e2e8bed774 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--contentTemplates.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--contentTemplates.ftl @@ -4,3 +4,4 @@ <#include "pageManagement--sparqlQuery.ftl"> <#include "pageManagement--fixedHtml.ftl"> <#include "pageManagement--searchIndividuals.ftl"> +<#include "pageManagement--browseSearchFilterValues.ftl"> diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--customDataScript.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--customDataScript.ftl index eeeb8a9cd6..1701c8c71c 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--customDataScript.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--customDataScript.ftl @@ -30,6 +30,7 @@ scripts list.--> dataGetterLabelToURI:{ //maps labels to URIs "browseClassGroup": "java:edu.cornell.mannlib.vitro.webapp.utils.dataGetter.ClassGroupPageData", + "searchFilterValues": "java:edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SearchFilterValuesDataGetter", "individualsForClasses": "java:edu.cornell.mannlib.vitro.webapp.utils.dataGetter.IndividualsForClassesDataGetter", "sparqlQuery":"java:edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SparqlQueryDataGetter", "fixedHtml":"java:edu.cornell.mannlib.vitro.webapp.utils.dataGetter.FixedHTMLDataGetter", diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl index 550ae8b398..81172b6a32 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl @@ -79,6 +79,7 @@