diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java index 718dbe8e0ce6..b9966bb52223 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java @@ -19,7 +19,12 @@ */ package com.xpn.xwiki.api; +import java.util.Map; + +import org.xwiki.evaluation.ObjectEvaluator; +import org.xwiki.evaluation.ObjectEvaluatorException; import org.xwiki.model.reference.ObjectPropertyReference; +import org.xwiki.stability.Unstable; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; @@ -159,4 +164,18 @@ public ObjectPropertyReference getPropertyReference(String propertyName) { return new ObjectPropertyReference(propertyName, getReference()); } + + /** + * Evaluates the properties of an object using a matching implementation of {@link ObjectEvaluator}. + * + * @return a Map storing the evaluated properties + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ + @Unstable + public Map evaluate() throws ObjectEvaluatorException + { + return getBaseObject().evaluate(); + } } diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/objects/BaseObject.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/objects/BaseObject.java index 6824fa4d11e6..f4467009c263 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/objects/BaseObject.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/objects/BaseObject.java @@ -22,15 +22,19 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dom4j.Element; +import org.xwiki.evaluation.ObjectEvaluator; +import org.xwiki.evaluation.ObjectEvaluatorException; import org.xwiki.model.EntityType; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.SpaceReference; +import org.xwiki.stability.Unstable; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; @@ -435,4 +439,20 @@ protected void mergeField(PropertyInterface currentElement, ElementInterface pre super.mergeField(currentElement, previousElement, newElement, configuration, context, mergeResult); } + + /** + * Evaluates the properties of an object using a matching implementation of {@link ObjectEvaluator}. + * + * @return a Map storing the evaluated properties + * @throws ObjectEvaluatorException if the evaluation fails + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ + @Unstable + public Map evaluate() throws ObjectEvaluatorException + { + ObjectEvaluator objectEvaluator = Utils.getComponent(ObjectEvaluator.class); + return objectEvaluator.evaluate(this); + } } diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluator.java new file mode 100644 index 000000000000..683e854e281b --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluator.java @@ -0,0 +1,55 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.evaluation; + +import java.util.Map; + +import org.xwiki.component.annotation.Role; +import org.xwiki.evaluation.internal.DefaultObjectEvaluator; +import org.xwiki.stability.Unstable; + +import com.xpn.xwiki.objects.BaseObject; + +/** + * Evaluates the properties of an object and returns a Map that stores the evaluation results, in which keys are the + * field names of the properties, and values their evaluated content. + * Implement an instance with a hint corresponding to the class name of the object you want to evaluate and use + * {@link DefaultObjectEvaluator} that will proxy the calls to the right implementation. + * This ensures that the properties being evaluated are only the ones referenced explicitly by the provided + * implementation, in order to avoid accidental evaluation of properties that should only be used in specific contexts. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Unstable +@Role +public interface ObjectEvaluator +{ + /** + * Evaluates the properties of an object. + * + * @param object the object to evaluate + * @return a Map storing the evaluated properties + * @throws ObjectEvaluatorException if the evaluation fails + */ + Map evaluate(BaseObject object) throws ObjectEvaluatorException; +} diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluatorException.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluatorException.java new file mode 100644 index 000000000000..89e57029fee1 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectEvaluatorException.java @@ -0,0 +1,55 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.evaluation; + +import org.xwiki.stability.Unstable; + +/** + * Exception raised during evaluation of XObjects properties. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Unstable +public class ObjectEvaluatorException extends Exception +{ + /** + * Creates an instance of ObjectEvaluatorException with a message and a cause. + * + * @param message the message detailing the issue + * @param cause the cause of the exception + */ + public ObjectEvaluatorException(String message, Throwable cause) + { + super(message, cause); + } + + /** + * Creates an instance of ObjectEvaluatorException with a message. + * + * @param message the message detailing the issue + */ + public ObjectEvaluatorException(String message) + { + this(message, null); + } +} diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectPropertyEvaluator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectPropertyEvaluator.java new file mode 100644 index 000000000000..fd680dc193f8 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/ObjectPropertyEvaluator.java @@ -0,0 +1,52 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.evaluation; + +import java.util.Map; + +import org.xwiki.component.annotation.Role; +import org.xwiki.stability.Unstable; + +import com.xpn.xwiki.objects.BaseObject; + +/** + * Evaluates the properties of an object and returns a Map that stores the evaluation results, in which keys are the + * field names of the properties, and values their evaluated content. + * Instances of this interface should be used as helpers by implementations of {@link ObjectEvaluator}. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Unstable +@Role +public interface ObjectPropertyEvaluator +{ + /** + * Evaluates the properties of an object. + * + * @param object the object to evaluate + * @param properties the names of the properties to evaluate + * @return a Map storing the evaluated properties + * @throws ObjectEvaluatorException if the evaluation fails + */ + Map evaluateProperties(BaseObject object, String... properties) throws ObjectEvaluatorException; +} diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/DefaultObjectEvaluator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/DefaultObjectEvaluator.java new file mode 100644 index 000000000000..d2cbae62f22c --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/DefaultObjectEvaluator.java @@ -0,0 +1,82 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.evaluation.internal; + +import java.util.Collections; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.component.manager.ComponentLookupException; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.evaluation.ObjectEvaluator; +import org.xwiki.evaluation.ObjectEvaluatorException; +import org.xwiki.model.reference.EntityReferenceSerializer; + +import com.xpn.xwiki.objects.BaseObject; + +/** + * Evaluator that proxies the actual evaluation to the right ObjectEvaluator implementation, based on the XClass of + * the object. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Component +@Singleton +public class DefaultObjectEvaluator implements ObjectEvaluator +{ + @Inject + @Named("context") + private Provider contextComponentManagerProvider; + + @Inject + @Named("local") + private EntityReferenceSerializer entityReferenceSerializer; + + @Override + public Map evaluate(BaseObject object) throws ObjectEvaluatorException + { + if (object == null) { + return Collections.emptyMap(); + } + + String xClassName = this.entityReferenceSerializer.serialize(object.getXClassReference()); + ComponentManager componentManager = this.contextComponentManagerProvider.get(); + if (!componentManager.hasComponent(ObjectEvaluator.class, xClassName)) { + throw new ObjectEvaluatorException(String.format("Could not find an instance of 'ObjectEvaluator' for " + + "XObject of class '%s'.", xClassName)); + } + + try { + ObjectEvaluator objectEvaluator = componentManager.getInstance(ObjectEvaluator.class, xClassName); + return objectEvaluator.evaluate(object); + } catch (ComponentLookupException e) { + throw new ObjectEvaluatorException(String.format("Could not instantiate 'ObjectEvaluator' for XObject of " + + "class '%s'.", xClassName), e); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/VelocityObjectPropertyEvaluator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/VelocityObjectPropertyEvaluator.java new file mode 100644 index 000000000000..d569f903f8a3 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/evaluation/internal/VelocityObjectPropertyEvaluator.java @@ -0,0 +1,112 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.evaluation.internal; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.velocity.VelocityContext; +import org.xwiki.component.annotation.Component; +import org.xwiki.evaluation.ObjectEvaluatorException; +import org.xwiki.evaluation.ObjectPropertyEvaluator; +import org.xwiki.model.document.DocumentAuthors; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.security.authorization.AuthorExecutor; +import org.xwiki.security.authorization.AuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.user.UserReferenceSerializer; +import org.xwiki.velocity.VelocityManager; + +import com.xpn.xwiki.objects.BaseObject; + +/** + * ObjectPropertyEvaluator that supports the evaluation of Velocity properties. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Component +@Singleton +@Named("velocity") +public class VelocityObjectPropertyEvaluator implements ObjectPropertyEvaluator +{ + @Inject + private AuthorizationManager authorizationManager; + + @Inject + private AuthorExecutor authorExecutor; + + @Inject + private VelocityManager velocityManager; + + @Inject + @Named("document") + private UserReferenceSerializer documentUserSerializer; + + @Inject + private EntityReferenceSerializer entityReferenceSerializer; + + /** + * Evaluates Velocity properties of an object with an author executor. + */ + @Override + public Map evaluateProperties(BaseObject object, String... properties) + throws ObjectEvaluatorException + { + Map evaluatedProperties = new HashMap<>(); + for (String property : properties) { + evaluatedProperties.put(property, object.getStringValue(property)); + } + + DocumentReference documentReference = object.getDocumentReference(); + DocumentAuthors documentAuthors = object.getOwnerDocument().getAuthors(); + DocumentReference authorReference = + this.documentUserSerializer.serialize(documentAuthors.getEffectiveMetadataAuthor()); + + if (this.authorizationManager.hasAccess(Right.SCRIPT, authorReference, documentReference)) { + try { + this.authorExecutor.call(() -> { + VelocityContext context = this.velocityManager.getVelocityContext(); + for (Map.Entry propertyEntry : evaluatedProperties.entrySet()) { + StringWriter writer = new StringWriter(); + String serializedPropertyReference = this.entityReferenceSerializer.serialize( + object.getField(propertyEntry.getKey()).getReference()); + this.velocityManager.getVelocityEngine().evaluate(context, writer, serializedPropertyReference, + propertyEntry.getValue()); + propertyEntry.setValue(writer.toString()); + } + return null; + }, authorReference, documentReference); + } catch (Exception e) { + throw new ObjectEvaluatorException("Failed to run Velocity engine.", e); + } + } + + return evaluatedProperties; + } +} diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt index 4ca39fe474e5..930029d49af0 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt @@ -276,3 +276,5 @@ org.xwiki.security.authservice.internal.AuthServiceConfigurationInvalidator org.xwiki.security.authservice.internal.AuthServiceManager org.xwiki.security.authservice.internal.StandardXWikiAuthServiceComponent org.xwiki.security.authservice.script.AuthServiceScriptService +org.xwiki.evaluation.internal.DefaultObjectEvaluator +org.xwiki.evaluation.internal.VelocityObjectPropertyEvaluator diff --git a/xwiki-platform-core/xwiki-platform-search/pom.xml b/xwiki-platform-core/xwiki-platform-search/pom.xml index 954f30a23468..8235cc788034 100644 --- a/xwiki-platform-core/xwiki-platform-search/pom.xml +++ b/xwiki-platform-core/xwiki-platform-search/pom.xml @@ -32,6 +32,7 @@ pom XWiki Platform - Search - Parent POM + xwiki-platform-search-api xwiki-platform-search-solr xwiki-platform-search-ui diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/pom.xml b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/pom.xml new file mode 100644 index 000000000000..627550765592 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/pom.xml @@ -0,0 +1,44 @@ + + + + 4.0.0 + + org.xwiki.platform + xwiki-platform-search + 16.0-SNAPSHOT + + xwiki-platform-search-api + XWiki Platform - Search - API + jar + XWiki Platform - Search - API + + + org.xwiki.platform + xwiki-platform-oldcore + ${project.version} + + + org.xwiki.commons + xwiki-commons-component-api + ${commons.version} + + + diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/java/org/xwiki/search/internal/SearchSuggestSourceObjectEvaluator.java b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/java/org/xwiki/search/internal/SearchSuggestSourceObjectEvaluator.java new file mode 100644 index 000000000000..99ea9459ca6b --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/java/org/xwiki/search/internal/SearchSuggestSourceObjectEvaluator.java @@ -0,0 +1,63 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.search.internal; + +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.evaluation.ObjectEvaluator; +import org.xwiki.evaluation.ObjectEvaluatorException; +import org.xwiki.evaluation.ObjectPropertyEvaluator; + +import com.xpn.xwiki.objects.BaseObject; + +/** + * Evaluator for objects of class {@code XWiki.SearchSuggestSourceClass}. + * Returns a Map storing the evaluated content for "name" and "icon" properties. + * + * @version $Id$ + * @since 14.10.21 + * @since 15.5.5 + * @since 15.10.2 + */ +@Component +@Singleton +@Named(SearchSuggestSourceObjectEvaluator.ROLE_HINT) +public class SearchSuggestSourceObjectEvaluator implements ObjectEvaluator +{ + /** + * The role hint of this component. + */ + public static final String ROLE_HINT = "XWiki.SearchSuggestSourceClass"; + + @Inject + @Named("velocity") + private ObjectPropertyEvaluator velocityPropertyEvaluator; + + @Override + public Map evaluate(BaseObject object) throws ObjectEvaluatorException + { + return this.velocityPropertyEvaluator.evaluateProperties(object, "name", "icon"); + } +} diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/resources/META-INF/components.txt new file mode 100644 index 000000000000..e4698e30df7d --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-api/src/main/resources/META-INF/components.txt @@ -0,0 +1 @@ +org.xwiki.search.internal.SearchSuggestSourceObjectEvaluator diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/pom.xml b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/pom.xml index 2697f5be21f4..d7eeb17d421c 100644 --- a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/pom.xml +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/pom.xml @@ -41,6 +41,11 @@ xwiki-platform-uiextension-api ${project.version} + + org.xwiki.platform + xwiki-platform-search-api + ${project.version} + org.webjars @@ -86,5 +91,17 @@ ${project.version} test + + org.xwiki.commons + xwiki-commons-tool-test-component + ${commons.version} + test + + + org.xwiki.platform + xwiki-platform-oldcore + ${project.version} + test + diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestConfigSheet.xml b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestConfigSheet.xml index d459a649360d..52ab788d3a77 100644 --- a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestConfigSheet.xml +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/main/resources/XWiki/SearchSuggestConfigSheet.xml @@ -44,19 +44,20 @@ #end #macro (displaySearchSuggestSource $source) + #set ($evaluatedSource = $source.evaluate()) #set ($icon = $source.getProperty('icon').value) #if ($icon.startsWith('icon:')) #set ($icon = $xwiki.getSkinFile("icons/silk/${icon.substring(5)}.png")) #else ## Evaluate the Velocity code for backward compatibility. - #set ($icon = "#evaluate($icon)") + #set ($icon = $evaluatedSource.icon) #end #set ($name = $source.getProperty('name').value) #if ($services.localization.get($name)) #set ($name = $services.localization.render($name)) #else ## Evaluate the Velocity code for backward compatibility. - #set ($name = "#evaluate($name)") + #set ($name = $evaluatedSource.name) #end #set ($style = 'source-header') #if ("$source.getProperty('activated').value" == '1') @@ -67,9 +68,9 @@ #end <li class="source"> <div class="$style"> - <img class="icon" src="$!icon" alt="" /> - <span class="limit">$!source.getProperty('resultsNumber').value</span> - <span class="name">$!name</span> + <img class="icon" src="$escapetool.xml($!icon)" alt="" /> + <span class="limit">$escapetool.xml($!source.getProperty('resultsNumber').value)</span> + <span class="name">$escapetool.xml($!name)</span> #if ($editing) <div class="actions"> <a class="delete" href="$doc.getURL('objectremove', $escapetool.url({ @@ -137,8 +138,9 @@ <ul class="nav nav-tabs searchEngines" role="tablist"> #foreach ($engine in $collectiontool.sort($sources.keySet())) <li#if ($engine == $searchEngine) class="active"#end role="presentation"> - <a href="#${engine}SearchSuggestSources" aria-controls="${engine}SearchSuggestSources" - role="tab" data-toggle="tab">$engine</a> + #set ($escapedEngine = $escapetool.xml($engine)) + <a href="#${escapedEngine}SearchSuggestSources" aria-controls="${escapedEngine}SearchSuggestSources" + role="tab" data-toggle="tab">$escapedEngine</a> </li> #end </ul> @@ -150,7 +152,9 @@ ## We can't use the UL element as tab panel because we break the HTML validation tests: "Bad value 'tabpanel' for ## attribute 'role' on element 'ul'". I don't understand the reason as there's no constraint on ## https://www.w3.org/TR/2010/WD-wai-aria-20100916/roles#tabpanel . - <div class="tab-pane#if ($engine == $searchEngine) active#end" role="tabpanel" id="${engine}SearchSuggestSources"> + #set ($escapedEngine = $escapetool.xml($engine)) + <div class="tab-pane#if ($engine == $searchEngine) active#end" role="tabpanel" + id="${escapedEngine}SearchSuggestSources"> <ul class="searchSuggestSources"> #foreach ($source in $sources.get($engine)) #displaySearchSuggestSource($source) diff --git a/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestConfigSheetPageTest.java b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestConfigSheetPageTest.java new file mode 100644 index 000000000000..9b1c5a24fb25 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-ui/src/test/java/org/xwiki/search/ui/SearchSuggestConfigSheetPageTest.java @@ -0,0 +1,188 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.search.ui; + +import java.util.concurrent.Callable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xwiki.evaluation.internal.DefaultObjectEvaluator; +import org.xwiki.evaluation.internal.VelocityObjectPropertyEvaluator; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.rendering.RenderingScriptServiceComponentList; +import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList; +import org.xwiki.search.internal.SearchSuggestSourceObjectEvaluator; +import org.xwiki.security.authorization.AuthorExecutor; +import org.xwiki.security.authorization.AuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.test.annotation.ComponentList; +import org.xwiki.test.junit5.mockito.MockComponent; +import org.xwiki.test.page.HTML50ComponentList; +import org.xwiki.test.page.PageTest; +import org.xwiki.test.page.TestNoScriptMacro; +import org.xwiki.test.page.XWikiSyntax21ComponentList; +import org.xwiki.velocity.VelocityEngine; +import org.xwiki.velocity.VelocityManager; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.objects.BaseObject; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RenderingScriptServiceComponentList +@DefaultRenderingConfigurationComponentList +@HTML50ComponentList +@XWikiSyntax21ComponentList +@ComponentList({ + DefaultObjectEvaluator.class, + VelocityObjectPropertyEvaluator.class, + SearchSuggestSourceObjectEvaluator.class, + TestNoScriptMacro.class, +}) +public class SearchSuggestConfigSheetPageTest extends PageTest +{ + private static final String WIKI_NAME = "xwiki"; + + private static final DocumentReference SEARCH_SUGGEST_CONFIG_SHEET = + new DocumentReference(WIKI_NAME, "XWiki", "SearchSuggestConfigSheet"); + + private static final DocumentReference SEARCH_SUGGEST_SOURCE_CLASS = + new DocumentReference(WIKI_NAME, "XWiki", "SearchSuggestSourceClass"); + + private static final DocumentReference TEST_PAGE = + new DocumentReference(WIKI_NAME, "Test", "TestDocument"); + + private static final DocumentReference AUTHOR_REFERENCE = + new DocumentReference(WIKI_NAME, "XWiki", "TestUser"); + + @MockComponent + private AuthorizationManager authorizationManager; + + private AuthorExecutor authorExecutor; + + private VelocityEngine velocityEngine; + + private XWikiDocument testPageDocument; + + private XWikiDocument searchSuggestConfigSheetDocument; + + private String testString; + + private String evaluatedTestString; + + @BeforeEach + void setUp() throws Exception + { + this.xwiki.initializeMandatoryDocuments(this.context); + loadPage(SEARCH_SUGGEST_SOURCE_CLASS); + loadPage(SEARCH_SUGGEST_CONFIG_SHEET); + + this.testString = "$doc.getDocumentReference().getName(){{/html}}{{noscript /}}"; + this.evaluatedTestString = TEST_PAGE.getName() + "{{/html}}{{noscript /}}"; + + this.searchSuggestConfigSheetDocument = this.xwiki.getDocument(SEARCH_SUGGEST_CONFIG_SHEET, this.context); + + this.testPageDocument = this.xwiki.getDocument(TEST_PAGE, this.context); + this.testPageDocument.setTitle("Test Search Suggestions"); + this.testPageDocument.setAuthorReference(AUTHOR_REFERENCE); + BaseObject searchSuggestSourceObject = + this.testPageDocument.newXObject(SEARCH_SUGGEST_SOURCE_CLASS, this.context); + searchSuggestSourceObject.setStringValue("name", this.testString); + searchSuggestSourceObject.setStringValue("icon", this.testString); + searchSuggestSourceObject.setStringValue("resultsNumber", this.testString); + searchSuggestSourceObject.setStringValue("engine", this.testString); + this.xwiki.saveDocument(this.testPageDocument, this.context); + + this.authorExecutor = this.componentManager.registerMockComponent(AuthorExecutor.class, true); + + // Spy Velocity Engine. + VelocityManager velocityManager = this.componentManager.getInstance(VelocityManager.class); + this.velocityEngine = velocityManager.getVelocityEngine(); + this.velocityEngine = spy(this.velocityEngine); + velocityManager = spy(velocityManager); + this.componentManager.registerComponent(VelocityManager.class, velocityManager); + when(velocityManager.getVelocityEngine()).thenReturn(this.velocityEngine); + + when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> { + Callable callable = invocation.getArgument(0); + return callable.call(); + }); + } + + @Test + void displaySourceWithoutScriptRights() throws Exception + { + when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, TEST_PAGE)).thenReturn(false); + + this.context.setDoc(this.testPageDocument); + Document result = renderHTMLPage(this.searchSuggestConfigSheetDocument); + + verify(this.authorizationManager).hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, TEST_PAGE); + verify(this.velocityEngine, never()).evaluate(any(), any(), any(), eq(this.testString)); + + Element presentationLink = + result.getElementsByAttributeValue("role", "presentation").get(0).getElementsByTag("a").get(0); + // Escaping tests only. + assertEquals("#" + this.testString + "SearchSuggestSources", presentationLink.attr("href")); + assertEquals(this.testString, presentationLink.text()); + assertEquals(this.testString + "SearchSuggestSources", presentationLink.attr("aria-controls")); + assertEquals(this.testString + "SearchSuggestSources", result.getElementsByClass("tab-pane").get(0).attr("id")); + assertEquals(this.testString, result.getElementsByClass("limit").text()); + + // These should not be evaluated. + assertEquals(this.testString, result.getElementsByClass("icon").get(0).attr("src")); + assertEquals(this.testString, result.getElementsByClass("name").text()); + } + + @Test + void displaySourceWithScriptRights() throws Exception + { + when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, TEST_PAGE)).thenReturn(true); + + this.context.setDoc(this.testPageDocument); + Document result = renderHTMLPage(this.searchSuggestConfigSheetDocument); + + verify(this.authorizationManager).hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, TEST_PAGE); + verify(this.authorExecutor).call(any(), eq(AUTHOR_REFERENCE), eq(TEST_PAGE)); + verify(this.velocityEngine, times(2)).evaluate(any(), any(), any(), eq(this.testString)); + + Element presentationLink = + result.getElementsByAttributeValue("role", "presentation").get(0).getElementsByTag("a").get(0); + // Escaping tests only. + assertEquals("#" + this.testString + "SearchSuggestSources", presentationLink.attr("href")); + assertEquals(this.testString, presentationLink.text()); + assertEquals(this.testString + "SearchSuggestSources", presentationLink.attr("aria-controls")); + assertEquals(this.testString + "SearchSuggestSources", result.getElementsByClass("tab-pane").get(0).attr("id")); + assertEquals(this.testString, result.getElementsByClass("limit").text()); + + // These should be evaluated. + assertEquals(this.evaluatedTestString, result.getElementsByClass("icon").get(0).attr("src")); + assertEquals(this.evaluatedTestString, result.getElementsByClass("name").text()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/uicomponents/search/searchSuggest.js b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/uicomponents/search/searchSuggest.js index 210d65223978..eb3260294a39 100644 --- a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/uicomponents/search/searchSuggest.js +++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/uicomponents/search/searchSuggest.js @@ -195,19 +195,20 @@ var XWiki = (function (XWiki) { #end #set ($discard = $xwiki.getDocument('XWiki.SearchCode').getRenderedContent()) #if ($engine == $searchEngine) + #set ($evaluatedSource = $source.evaluate()) #set ($name = $source.getProperty('name').value) #if ($services.localization.get($name)) #set ($name = $services.localization.render($name)) #else ## Evaluate the Velocity code for backward compatibility. - #set ($name = "#evaluate($name)") + #set ($name = $evaluatedSource.name) #end #set ($icon = $source.getProperty('icon').value) #if ($icon.startsWith('icon:')) #set ($icon = $xwiki.getSkinFile("icons/silk/${icon.substring(5)}.png")) #else ## Evaluate the Velocity code for backward compatibility. - #set ($icon = "#evaluate($icon)") + #set ($icon = $evaluatedSource.icon) #end #set ($service = $source.getProperty('url').value) #set ($parameters = {