CVE-2023-35152 (published 2023-06-23T17:15Z)

XWiki Platform is a generic wiki platform. Starting in version 12.9-rc-1 and prior to versions 14.4.8, 14.10.6, and 15.1, any logged in user can add dangerous content in their first name field and see it executed with programming rights. Leading to rights escalation. The vulnerability has been fixed on XWiki 14.4.8, 14.10.6, and 15.1. As a workaround, one may apply the patch manually.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Simon Urli
author_date: 2020-10-13 10:41:46+02:00
msg: XWIKI-17733: Use a LiveTable to display the page liked in user profile
XWIKI-17724: Display Likers in a LiveTable

  * Use livetables in Likes UI
  * Add a new missing method to count user likes globally for allowing
    pagination
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -494,24 +494,29 @@
                 <new>method java.util.Optional&lt;org.xwiki.ratings.Rating&gt; org.xwiki.ratings.script.AbstractScriptRatingsManager::setRating(org.xwiki.model.reference.EntityReference, int) @ org.xwiki.ratings.script.RatingsScriptService</new>
                 <justification>Redesign of Ratings API: use new best practices for script service return if type is nullable.</justification>
               </item>
-                <item>
+              <item>
                 <code>java.method.parameterTypeChanged</code>
                 <old>parameter org.xwiki.ratings.script.RatingApi org.xwiki.ratings.script.RatingsScriptService::setRating(===org.xwiki.model.reference.DocumentReference===, org.xwiki.model.reference.DocumentReference, int)</old>
                 <new>parameter java.util.Optional&lt;org.xwiki.ratings.Rating&gt; org.xwiki.ratings.script.AbstractScriptRatingsManager::setRating(===org.xwiki.model.reference.EntityReference===, org.xwiki.user.UserReference, int) @ org.xwiki.ratings.script.RatingsScriptService</new>
                 <justification>Redesign of Ratings API: allow to set rating for any reference (not only DocumentReference)</justification>
               </item>
-                <item>
+              <item>
                 <code>java.method.parameterTypeChanged</code>
                 <old>parameter org.xwiki.ratings.script.RatingApi org.xwiki.ratings.script.RatingsScriptService::setRating(org.xwiki.model.reference.DocumentReference, ===org.xwiki.model.reference.DocumentReference===, int)</old>
                 <new>parameter java.util.Optional&lt;org.xwiki.ratings.Rating&gt; org.xwiki.ratings.script.AbstractScriptRatingsManager::setRating(org.xwiki.model.reference.EntityReference, ===org.xwiki.user.UserReference===, int) @ org.xwiki.ratings.script.RatingsScriptService</new>
                 <justification>Redesign of Ratings API: use new best practices for specifying a user by using the UserReference.</justification>
               </item>
-                <item>
+              <item>
                 <code>java.method.returnTypeChanged</code>
                 <old>method org.xwiki.ratings.script.RatingApi org.xwiki.ratings.script.RatingsScriptService::setRating(org.xwiki.model.reference.DocumentReference, org.xwiki.model.reference.DocumentReference, int)</old>
                 <new>method java.util.Optional&lt;org.xwiki.ratings.Rating&gt; org.xwiki.ratings.script.AbstractScriptRatingsManager::setRating(org.xwiki.model.reference.EntityReference, org.xwiki.user.UserReference, int) @ org.xwiki.ratings.script.RatingsScriptService</new>
                 <justification>Redesign of Ratings API: use new best practices for script service return if type is nullable.</justification>
               </item>
+              <item>
+                <code>java.method.addedToInterface</code>
+                <new>method long org.xwiki.like.LikeManager::countUserLikes(org.xwiki.user.UserReference) throws org.xwiki.like.LikeException</new>
+                <justification>Unstable API.</justification>
+              </item>
             </revapi.ignore>
           </analysisConfiguration>
         </configuration>

programming_language: CoffeeScript
filename: LikeManager.java
change_type: ModificationType.MODIFY
diff: @@ -58,6 +58,17 @@
      */
     List<EntityReference> getUserLikes(UserReference source, int offset, int limit) throws LikeException;
 
+    /**
+     * Retrieve the total number of likes performed by a user.
+     *
+     * @param source the user who performs the likes to count.
+     * @return the total number of likes performed.
+     * @throws LikeException in case of problem when getting the information.
+     * @since 12.9RC1
+     */
+    @Unstable
+    long countUserLikes(UserReference source) throws LikeException;
+
     /**
      * Retrieve like information a specific entity.
      *
@@ -95,7 +106,9 @@
      * @param limit the limit used for pagination.
      * @return a list of user references of users who liked this page.
      * @throws LikeException in case of problem for performing the query.
+     * @since 12.9RC1
      */
+    @Unstable
     List<UserReference> getLikers(EntityReference target, int offset, int limit) throws LikeException;
 
     /**

programming_language: Java
filename: DefaultLikeManager.java
change_type: ModificationType.MODIFY
diff: @@ -34,8 +34,6 @@
 import org.xwiki.cache.CacheManager;
 import org.xwiki.cache.config.LRUCacheConfiguration;
 import org.xwiki.component.annotation.Component;
-import org.xwiki.component.manager.ComponentLifecycleException;
-import org.xwiki.component.phase.Disposable;
 import org.xwiki.component.phase.Initializable;
 import org.xwiki.component.phase.InitializationException;
 import org.xwiki.like.LikeConfiguration;
@@ -65,7 +63,7 @@
  */
 @Component
 @Singleton
-public class DefaultLikeManager implements LikeManager, Initializable, Disposable
+public class DefaultLikeManager implements LikeManager, Initializable
 {
     private static final int DEFAULT_LIKE_VOTE = 1;
 
@@ -126,12 +124,6 @@ public void initialize() throws InitializationException
         }
     }
 
-    @Override
-    public void dispose() throws ComponentLifecycleException
-    {
-        //this.authorizationManager.
-    }
-
     private String getExistCacheKey(UserReference source, EntityReference target)
     {
         return String.format("%s_%s",
@@ -176,6 +168,18 @@ public List<EntityReference> getUserLikes(UserReference source, int offset, int
         }
     }
 
+    @Override
+    public long countUserLikes(UserReference source) throws LikeException
+    {
+        try {
+            return this.ratingsManager.countRatings(
+                Collections.singletonMap(RatingsManager.RatingQueryField.USER_REFERENCE, source));
+        } catch (RatingsException e) {
+            throw new LikeException(
+                String.format("Error when trying to count user likes for user [%s]", source), e);
+        }
+    }
+
     @Override
     public long getEntityLikes(EntityReference target) throws LikeException
     {

programming_language: Java
filename: LikeScriptService.java
change_type: ModificationType.MODIFY
diff: @@ -214,6 +214,26 @@ public List<EntityReference> getUserLikes(UserReference userReference, int offse
         return Collections.emptyList();
     }
 
+    /**
+     * Count the number of likes performed by the given user.
+     *
+     * @param userReference the user for whom to count likes.
+     * @return the number of likes performed.
+     * @since 12.9RC1
+     */
+    @Unstable
+    public Optional<Long> countUserLikes(UserReference userReference)
+    {
+        Optional<Long> result = Optional.empty();
+        try {
+            result = Optional.of(this.likeManager.countUserLikes(userReference));
+        } catch (LikeException e) {
+            this.logger.warn("Error while counting likes for user [{}]", userReference,
+                ExceptionUtils.getRootCause(e));
+        }
+        return result;
+    }
+
     /**
      * Determine if the current user already liked the given reference.
      * @param entityReference the reference for which to check if the current liked it or not already.

programming_language: Java
filename: likers.vm
change_type: ModificationType.MODIFY
diff: @@ -17,19 +17,96 @@
 ## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 ## 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 ## ---------------------------------------------------------------------------
-#set ($likers = $services.like.getLikers($doc, 0, 50))
-<div id="document-title"><h1>$services.localization.render('like.likers.title', [$doc.title])</h1></div>
-<div id="likers-content">
-    #if ($likers.isEmpty())
-        $services.localization.render('like.likers.empty')
+#if ($request.livetable == "true")
+    #macro (displayUserAliasWithAvatar $userReference $disabled)
+    <div class="user#if ($disabled) disabled#end" data-reference="$escapetool.xml($userReference)">
+    <span class="user-avatar-wrapper">
+      #getUserAvatarURL($userReference $avatarURL 120)
+      <img class="user-avatar" src="$escapetool.xml($avatarURL.url)" />
+    </span>
+      <a href="$xwiki.getURL($userReference)">$escapetool.xml($userReference.name)</a>
+    </div>
+    #end
+    $response.setContentType('application/json')
+    #set ($documentReference = $doc.documentReference)
+    ##==============================
+    ## Offset = item # at which to start displaying data
+    ##==============================
+    #set($offset = $numbertool.toNumber($request.get('offset')))
+    ## offset starts from 0 in velocity and 1 in javascript
+    #set($offset = $offset - 1)
+    #if($offset < 0)
+        #set($offset = 0)
+    #end
+    ##==================
+    ## Limit = # of items to display
+    ##==================
+    #set($limit = $numbertool.toNumber($request.get('limit')))
+    ##==========
+    ## Sort direction
+    ##==========
+    #set($order = "$!request.sort")
+    #if($order != '')
+        #set($orderDirection = "$!{request.get('dir').toLowerCase()}")
+        #if("$!orderDirection" != '' && "$!orderDirection" != 'asc')
+            #set($orderDirection = 'desc')
+        #end
+    #end
+    #set ($likeRecords = $services.like.getLikers($documentReference, $offset, $limit))
+    #set ($userRows = [])
+    #foreach($userReference in $likeRecords)
+        #set ($grayed = $xcontext.userReference == $userReference.reference)
+        #set ($userDoc = $xwiki.getDocument($userReference.reference))
+        #set ($userProperties = $services.user.getProperties($userReference))
+        #set ($userObject = $user.getObject('XWiki.XWikiUsers'))
+        #set ($row = {
+            'grayed': $grayed,
+            'doc_fullName': $userDoc.fullName,
+            'doc_wiki': $userDoc.wiki,
+            'doc_url': $userDoc.getURL(),
+            'doc_viewable': $services.security.authorization.hasAccess('view', $userDoc.documentReference),
+            'name': "#displayUserAliasWithAvatar($userDoc.documentReference $disabled)",
+            'first_name': $userProperties.firstName,
+            'last_name': $userProperties.lastName
+        })
+        #set ($discard = $userRows.add($row))
+    #end
+    ## ===
+    ## JSON
+    ## ===
+    #set ($newOffset = $offset + 1)
+    #set ($optLikesNumber = $services.like.getLikes($documentReference))
+    #if ($optLikesNumber.isPresent())
+      #set ($totalRows = $optLikesNumber.get())
     #else
-      <p>
-          $services.localization.render('like.likers.number', [$likers.size()])
-      </p>
-      <ul>
-          #foreach($liker in $likers)
-            <li>#displayUserLink($liker)</li>
-          #end
-      </ul>
+      #set ($totalRows = $likeRecords.size())
     #end
-</div>
\ No newline at end of file
+    {
+        "totalrows": $totalRows,
+        "returnedrows":  $likeRecords.size(),
+        "offset": $newOffset,
+        "reqNo": $numbertool.toNumber($request.reqNo),
+        "rows": $jsontool.serialize($userRows)
+    }
+#else
+    <h1>$escapetool.xml($services.localization.render('like.likers.title', [$doc.plainTitle]))</h1>
+    #set($columns = ["name", "first_name", "last_name"])
+    #set($columnsProperties = {
+        "name" : { "type" : "text", "sortable": false, "filterable": false, "html": true },
+        "first_name" : { "type" : "text", "sortable": false, "filterable": false},
+        "last_name" : { "type" : "text", "sortable": false, "filterable": false}
+    })
+    #set ($queryParams = {
+        "livetable": "true",
+        "xpage": "likers",
+        "outputSyntax": "plain"
+    })
+    ## We rely on the same column name than the Users administration, so we use same translation prefix for now.
+    #set($options = {
+        'url': $doc.getURL('get', $escapetool.url($queryParams)),
+        'outputOnlyHtml': true,
+        'translationPrefix' : "xe.admin.users."
+    })
+
+    #livetable("likers" $columns $columnsProperties $options)
+#end
\ No newline at end of file

programming_language: PowerShell
filename: DefaultLikeManagerTest.java
change_type: ModificationType.MODIFY
diff: @@ -308,4 +308,14 @@ void getLikers() throws Exception
         assertEquals(Arrays.asList(userReference1, userReference2, userReference3),
             this.defaultLikeManager.getLikers(this.target, 12, 4));
     }
+
+    @Test
+    void countUserLikes() throws Exception
+    {
+        when(this.ratingsManager.countRatings(
+            Collections.singletonMap(RatingsManager.RatingQueryField.USER_REFERENCE, this.userReference)))
+            .thenReturn(42L);
+
+        assertEquals(42L, this.defaultLikeManager.countUserLikes(this.userReference));
+    }
 }

programming_language: Java
filename: LikeScriptServiceTest.java
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@
 package org.xwiki.like.script;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 
@@ -54,6 +55,7 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -284,4 +286,22 @@ void cleanCache()
             Locale.ROOT);
         verify(this.asyncRendererCache).cleanCache(uixReference);
     }
+
+    @Test
+    void countUserLikes() throws LikeException
+    {
+        when(this.likeManager.countUserLikes(userReference)).thenReturn(43L);
+        assertEquals(Optional.of(43L), this.likeScriptService.countUserLikes(this.userReference));
+    }
+
+    @Test
+    void getUserLikes() throws LikeException
+    {
+        List<EntityReference> expectedList = Arrays.asList(
+            mock(EntityReference.class),
+            mock(EntityReference.class),
+            mock(EntityReference.class));
+        when(this.likeManager.getUserLikes(this.userReference, 2, 32)).thenReturn(expectedList);
+        assertEquals(expectedList, this.likeScriptService.getUserLikes(this.userReference, 2, 32));
+    }
 }

programming_language: Java
filename: LikeTranslations.de.xml
change_type: ModificationType.MODIFY
diff: @@ -57,5 +57,4 @@
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Die "Gefällt mir" Schaltfläche immer anzeigen - selbst wenn der Nutzer kein Recht hat diese zu benutzen. Anschalten, um die Anzahl der "Gefällt mir" immer anzuzeigen.
 XWiki.Like.LikeConfigurationClass_cacheCapacity=Cache-Größe
 XWiki.Like.LikeConfigurationClass_cacheCapacity.hint=Anzahl der "Gefällt mir"-Informationen im Cache. Sie müssen XWiki neustarten, damit veränderte Einstellungen angewendet werden.</content>
-  
 </xwikidoc>

programming_language: HTML
filename: LikeTranslations.es.xml
change_type: ModificationType.MODIFY
diff: @@ -57,5 +57,4 @@
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Mostrar el botón incluso si el usuario no tiene derecho de interaccionar con él.  Úsalo si deseas mostrar siempre el contador de "Me gusta".
 XWiki.Like.LikeConfigurationClass_cacheCapacity=Capacidad de la caché
 XWiki.Like.LikeConfigurationClass_cacheCapacity.hint=Número de información de "Me gusta" guardada en la caché. Ten en cuenta que tienes que reiniciar XWiki para que se activen los cambios de esta opción.</content>
-  
 </xwikidoc>

programming_language: Markdown
filename: LikeTranslations.fr.xml
change_type: ModificationType.MODIFY
diff: @@ -57,5 +57,4 @@
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Afficher le bouton même si l'utilisateur n'a pas les droits pour intéragir avec. Utilisez cette option si vous souhaitez toujours afficher le compteur de J'aime.
 XWiki.Like.LikeConfigurationClass_cacheCapacity=Capacité du Cache
 XWiki.Like.LikeConfigurationClass_cacheCapacity.hint=Nombre d'information J'aime à conserver dans le cache. Notez que vous devrez redémarrer XWiki pour que cette option soit prise en compte.</content>
-  
 </xwikidoc>

programming_language: Markdown
filename: LikeTranslations.no.xml
change_type: ModificationType.MODIFY
diff: @@ -57,5 +57,4 @@
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Vis knappen selv om brukeren ikke har rettigheter til å bruke den. Bruk hvis du alltid vil vise Liker-telleren.
 XWiki.Like.LikeConfigurationClass_cacheCapacity=Hurtigbufferkapasitet
 XWiki.Like.LikeConfigurationClass_cacheCapacity.hint=Antall Liker-informasjon som er lagret i hurtigbufferen. Merk at du må starte XWiki på nytt for at dette valget skal aktiveres.</content>
-  
 </xwikidoc>

programming_language: Markdown
filename: LikeTranslations.ru.xml
change_type: ModificationType.MODIFY
diff: @@ -57,5 +57,4 @@
 ### Missing: XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Display the button even if the user doesn't have the rights to interact with it. Use it if you wish to always display the Likes counter.
 ### Missing: XWiki.Like.LikeConfigurationClass_cacheCapacity=Cache capacity
 ### Missing: XWiki.Like.LikeConfigurationClass_cacheCapacity.hint=Number of Like information kept in cache. Note that you have to restart XWiki for this option change to be taken into account.</content>
-  
 </xwikidoc>

programming_language: Markdown
filename: LikeTranslations.xml
change_type: ModificationType.MODIFY
diff: @@ -52,7 +52,10 @@
 like.button.title.unlike=Click to unlike the current page. Number of likes on this page: {0}.
 like.likers.empty=No one likes this page yet.
 like.likers.number={0} person(s) like this page:
-like.likers.title=Likes on {0}
+like.likers.title=Likers of {0}
+like.livetable.column.title=Title
+like.livetable.column.location=Location
+like.livetable.column.likes=Likes
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton=Always display Like button
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton.hint=Display the button even if the user doesn't have the rights to interact with it. Use it if you wish to always display the Likes counter.
 XWiki.Like.LikeConfigurationClass_cacheCapacity=Cache capacity

programming_language: HTML
filename: LikeViewersMenuUIX.xml
change_type: ModificationType.MODIFY
diff: @@ -183,4 +183,4 @@
       <scope>wiki</scope>
     </property>
   </object>
-</xwikidoc>
\ No newline at end of file
+</xwikidoc>

programming_language: TypeScript
filename: UserProfileUIX.xml
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,96 @@
   <minorEdit>false</minorEdit>
   <syntaxId>xwiki/2.1</syntaxId>
   <hidden>true</hidden>
-  <content/>
+  <content>{{velocity}}
+#if ($request.livetable == "true")
+  #template('hierarchy_macros.vm')
+  #if("$!{request.xpage}" == 'plain')
+    $response.setContentType('application/json')
+  #end
+  #set ($documentReference = $services.model.resolveDocument($request.user))
+  ##==============================
+  ## Offset = item # at which to start displaying data
+  ##==============================
+  #set($offset = $numbertool.toNumber($request.get('offset')))
+  ## offset starts from 0 in velocity and 1 in javascript
+  #set($offset = $offset - 1)
+  #if($offset &lt; 0)
+    #set($offset = 0)
+  #end
+  ##==================
+  ## Limit = # of items to display
+  ##==================
+  #set($limit = $numbertool.toNumber($request.get('limit')))
+  #set ($likedPages = $services.like.getUserLikes($documentReference, $offset, $limit))
+  #set ($optUserLikes = $services.like.countUserLikes($documentReference))
+  #if ($optUserLikes.isPresent())
+    #set ($totalRows = $optUserLikes.get())
+  #else
+    #set ($totalRows = $likedPages.size())
+  #end
+  ##==========
+  ## Sort direction
+  ##==========
+  #set($order = "$!request.sort")
+  #if($order != '')
+    #set($orderDirection = "$!{request.get('dir').toLowerCase()}")
+    #if("$!orderDirection" != '' &amp;&amp; "$!orderDirection" != 'asc')
+      #set($orderDirection = 'desc')
+    #end
+  #end
+  #set ($pagesRows = [])
+  #foreach($likedPage in $likedPages)
+    #set ($likedDoc = $xwiki.getDocument($likedPage))
+    #set ($optDocumentLikes = $services.like.getLikes($likedPage))
+    #if ($optDocumentLikes.isPresent())
+      #set ($documentLikes = $optDocumentLikes.get())
+    #else
+      #set ($documentLikes = "N/A")
+    #end
+    ## code inspired from getdocuments.vm
+    #set ($viewable = $xwiki.hasAccessLevel('view', $xcontext.user, $services.model.serialize($likedPage, "default")))
+    #set ($row = {'doc_viewable' : $viewable})
+    #if (!$viewable)
+      #set ($discard = $row.put('doc_fullName', $likedDoc.fullName))
+    #else
+      #set ($translatedDoc = $likedDoc.translatedDocument)
+      #set ($fullname = $services.model.serialize($likedDoc.documentReference, 'default'))
+      #set ($discard = $row.put('doc_name', $likedDoc.documentReference.name))
+      #set ($discard = $row.put('doc_fullName', $fullname))
+      #set ($location = "#hierarchy($likedDoc.documentReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})")
+      #set ($discard = $row.put('doc_location', $location))
+      #set ($discard = $row.put('doc_space', $likedDoc.space))
+      #set ($discard = $row.put('doc_url', $xwiki.getURL($likedDoc)))
+      #set ($discard = $row.put('doc_space_url', $xwiki.getURL($services.model.createDocumentReference($!likedDoc.wiki, $!likedDoc.space, 'WebHome'))))
+      #set ($discard = $row.put('doc_wiki', $likedDoc.wiki))
+      #set ($discard = $row.put('doc_wiki_url', $xwiki.getURL($services.model.resolveDocument('', 'default', $likedDoc.documentReference.extractReference('WIKI')))))
+      #set ($discard = $row.put('doc_author_url', $xwiki.getURL($translatedDoc.author)))
+      #set ($discard = $row.put('doc_date', $xwiki.formatDate($translatedDoc.date)))
+      #set ($discard = $row.put('doc_title', $translatedDoc.plainTitle))
+      #set ($rawTitle = $translatedDoc.title)
+      #if ($rawTitle != $row['doc_title'])
+        #set ($discard = $row.put('doc_title_raw', $rawTitle))
+      #end
+      #set ($discard = $row.put('doc_author', $xwiki.getUserName($translatedDoc.author, false)))
+      #set ($discard = $row.put('doc_creationDate', $xwiki.formatDate($translatedDoc.creationDate)))
+      #set ($discard = $row.put('doc_creator', $xwiki.getUserName($translatedDoc.creator, false)))
+    #end
+    #set ($discard = $row.put('like', $documentLikes))
+    #set ($discard = $pagesRows.add($row))
+  #end
+  #set ($newOffset = $offset + 1)
+  ## ===
+  ## JSON
+  ## ===
+  {
+    "totalrows": $totalRows,
+    "returnedrows":  $pagesRows.size(),
+    "offset": $newOffset,
+    "reqNo": $numbertool.toNumber($request.reqNo),
+    "rows": $jsontool.serialize($pagesRows)
+  }
+#end
+{{/velocity}}</content>
   <object>
     <name>XWiki.Like.UserProfileUIX</name>
     <number>0</number>
@@ -66,6 +155,8 @@
         <cache>0</cache>
         <disabled>0</disabled>
         <displayType>select</displayType>
+        <freeText>forbidden</freeText>
+        <largeStorage>0</largeStorage>
         <multiSelect>1</multiSelect>
         <name>async_context</name>
         <number>4</number>
@@ -154,21 +245,39 @@
     </property>
     <property>
       <async_context>
+        <value>doc.reference</value>
         <value>user</value>
-        <value>wiki</value>
       </async_context>
     </property>
     <property>
-      <async_enabled>1</async_enabled>
+      <async_enabled>0</async_enabled>
     </property>
     <property>
       <content>{{velocity}}
-#set ($likedReferences = $services.like.getUserLikes($services.user.currentUserReference, 0, 50))
-|=Page
-#foreach ($likeReference in $likedReferences)
-  #set ($likedDocument = $xwiki.getDocument($likeReference))
-  |[[$likedDocument.title&gt;&gt;$services.model.serialize($likeReference)]]
-#end
+=$escapetool.xml($services.localization.render("like.userprofile.menu"))=
+#set($columns = ["doc.title", "doc.location", "like"])
+#set ($docTitleDisplayName = $escapetool.xml($services.localization.render('like.livetable.column.title')))
+#set ($docLocationDisplayName = $escapetool.xml($services.localization.render('like.livetable.column.location')))
+#set ($likesDisplayName = $escapetool.xml($services.localization.render('like.livetable.column.likes')))
+#set($columnsProperties = {
+  "doc.title" : { "displayName": $docTitleDisplayName, "type": "text", "size": 30, "link": "view", "sortable": false, "filterable": false },
+  "doc.location" : { "displayName": $docLocationDisplayName, "type": "text", "size": 30, "html": true, "sortable": false, "filterable": false },
+  "like" : { "displayName": $likesDisplayName, "type" : "number", "sortable": false, "filterable": false }
+})
+
+#set ($currentUser = $services.model.serialize($doc.documentReference, "default"))
+#set ($pageToCallReference = $services.model.resolveDocument("XWiki.Like.UserProfileUIX"))
+#set ($pageToCall = $xwiki.getDocument($pageToCallReference))
+#set ($queryParams = {
+  "user": $currentUser,
+  "livetable": "true",
+  "xpage": "plain",
+  "outputSyntax": "plain"
+})
+#set($options = {
+  'url': $pageToCall.getURL('get', $escapetool.url($queryParams))
+})
+#livetable("likedPages" $columns $columnsProperties $options)
 {{/velocity}}</content>
     </property>
     <property>

programming_language: PHP
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Manuel Leduc
author_date: 2023-02-20 14:26:23+01:00
msg: XWIKI-19900: Liked page whose FULLNAME contains dot(.) can not show in user profile.
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -70,5 +70,32 @@
       <scope>runtime</scope>
       <optional>true</optional>
     </dependency>
+    <!-- Test dependencies. -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-test-page</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-web-templates</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-like-api</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+      <type>test-jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-user-default</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+      <type>test-jar</type>
+    </dependency>
   </dependencies>
 </project>

programming_language: HTML
filename: LiveTableResultPage.xml
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,7 @@
   <minorEdit>false</minorEdit>
   <syntaxId>xwiki/2.1</syntaxId>
   <hidden>true</hidden>
-  <content>{{velocity}}
+  <content>{{velocity wiki="false"}}
 #if ($xcontext.action == 'get')
   #template('hierarchy_macros.vm')
   #if("$!{request.xpage}" == 'plain')

programming_language: PHP
filename: LiveTableResultPagePageTest.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,98 @@
+/*
+ * 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.like;
+
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.junit.jupiter.api.Test;
+import org.xwiki.like.script.LikeScriptServiceComponentList;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.model.script.ModelScriptService;
+import org.xwiki.ratings.RatingsManager;
+import org.xwiki.ratings.internal.DefaultRating;
+import org.xwiki.rendering.syntax.Syntax;
+import org.xwiki.template.script.TemplateScriptService;
+import org.xwiki.test.annotation.ComponentList;
+import org.xwiki.test.junit5.mockito.MockComponent;
+import org.xwiki.test.page.PageTest;
+import org.xwiki.test.page.XWikiSyntax21ComponentList;
+import org.xwiki.user.UserReferenceComponentList;
+
+import com.xpn.xwiki.doc.XWikiDocument;
+
+import net.sf.json.JSONObject;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test of {@code XWiki.Like.Code.LiveTableResultPage}.
+ *
+ * @version $Id$
+ * @since 15.1
+ * @since 14.10.6
+ */
+@XWikiSyntax21ComponentList
+@LikeScriptServiceComponentList
+@UserReferenceComponentList
+@ComponentList({
+    TemplateScriptService.class,
+    ModelScriptService.class
+})
+class LiveTableResultPagePageTest extends PageTest
+{
+    private static final DocumentReference DOCUMENT_REFERENCE =
+        new DocumentReference("xwiki", List.of("XWiki", "Like", "Code"), "LiveTableResultPage");
+
+    @MockComponent
+    @Named("solr")
+    private RatingsManager ratingsManager;
+
+    @Test
+    void likedPageWithADot() throws Exception
+    {
+        DocumentReference likedPageReference = new DocumentReference("xwiki", "Space", "With.ADot");
+
+        // Create an empty page to be returned as a liked page.
+        XWikiDocument likedDocument = this.xwiki.getDocument(likedPageReference, this.context);
+        this.xwiki.saveDocument(likedDocument, this.context);
+
+        // The rating manager is mocked for now as the focus of the tests is currently not to test the likes indexing.
+        when(this.ratingsManager.getRatings(anyMap(), anyInt(), anyInt(), any(RatingsManager.RatingQueryField.class),
+            anyBoolean())).thenReturn(List.of(new DefaultRating("id1").setReference(likedPageReference)));
+
+        setOutputSyntax(Syntax.PLAIN_1_0);
+        this.context.setAction("get");
+
+        this.request.put("offset", "1");
+        this.request.put("reqNo", "1");
+        this.request.put("limit", "10");
+
+        JSONObject object = renderJSONPage(DOCUMENT_REFERENCE);
+        
+        assertEquals("xwiki:Space.With\\.ADot", object.getJSONArray("rows").getJSONObject(0).getString("doc_fullName"));
+    }
+}

programming_language: Java