CVE-2023-35152: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: 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.
CWE 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.
CVSS v3 base score: 8.8
Published: 2023-06-23T17:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/pom.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/LikeManager.java, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/internal/DefaultLikeManager.java, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/script/LikeScriptService.java, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/resources/templates/likers.vm, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/internal/DefaultLikeManagerTest.java, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/script/LikeScriptServiceTest.java, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.de.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.es.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.fr.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.no.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.ru.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeViewersMenuUIX.xml, xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/UserProfileUIX.xml
Commit date: 2020-10-13 10:41:46 +0200
Commit message: 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

Diff: diff --git a/xwiki-platform-core/pom.xml b/xwiki-platform-core/pom.xml
index 075e3c801af93..531b17597adf3 100644
--- a/xwiki-platform-core/pom.xml
+++ b/xwiki-platform-core/pom.xml
@@ -492,28 +492,33 @@
                 <code>java.method.returnTypeChanged</code>
                 <old>method org.xwiki.ratings.script.RatingApi org.xwiki.ratings.script.RatingsScriptService::setRating(com.xpn.xwiki.api.Document, java.lang.String, int)</old>
                 <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>
       </plugin>
     </plugins>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/LikeManager.java b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/LikeManager.java
index de89f8321949e..0dd16bc289efd 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/LikeManager.java
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/LikeManager.java
@@ -56,10 +56,21 @@
      * @return a list of references liked by this user.
      * @throws LikeException in case of problem when getting the like.
      */
     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.
      *
      * @param target the page or object for which to retrieve the like information.
      * @return the number of likes for that entity.
@@ -93,11 +104,13 @@
      * @param target the page that has been liked.
      * @param offset the offset used for pagination.
      * @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;
 
     /**
      * @return a dedicated programmatic right for Like feature.
      */
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/internal/DefaultLikeManager.java b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/internal/DefaultLikeManager.java
index db28fc869d9e5..e9560fae1620a 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/internal/DefaultLikeManager.java
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/internal/DefaultLikeManager.java
@@ -32,12 +32,10 @@
 import org.xwiki.cache.Cache;
 import org.xwiki.cache.CacheException;
 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;
 import org.xwiki.like.LikeEvent;
 import org.xwiki.like.LikeException;
@@ -63,11 +61,11 @@
  * @version $Id$
  * @since 12.7RC1
  */
 @Component
 @Singleton
-public class DefaultLikeManager implements LikeManager, Initializable, Disposable
+public class DefaultLikeManager implements LikeManager, Initializable
 {
     private static final int DEFAULT_LIKE_VOTE = 1;
 
     @Inject
     private RatingsManagerFactory ratingsManagerFactory;
@@ -124,16 +122,10 @@ public void initialize() throws InitializationException
         } catch (RatingsException e) {
             throw new InitializationException("Error while trying to get the RankingManager.", e);
         }
     }
 
-    @Override
-    public void dispose() throws ComponentLifecycleException
-    {
-        //this.authorizationManager.
-    }
-
     private String getExistCacheKey(UserReference source, EntityReference target)
     {
         return String.format("%s_%s",
             this.userReferenceStringSerializer.serialize(source), this.entityReferenceSerializer.serialize(target));
     }
@@ -174,10 +166,22 @@ public List<EntityReference> getUserLikes(UserReference source, int offset, int
             throw new LikeException(
                 String.format("Error when trying to retrieve user likes for user [%s]", source), e);
         }
     }
 
+    @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
     {
         Long result = this.likeCountCache.get(this.entityReferenceSerializer.serialize(target));
         if (result == null) {
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/script/LikeScriptService.java b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/script/LikeScriptService.java
index 6de8f792a1186..448ab55ab8535 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/script/LikeScriptService.java
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/java/org/xwiki/like/script/LikeScriptService.java
@@ -212,10 +212,30 @@ public List<EntityReference> getUserLikes(UserReference userReference, int offse
                 ExceptionUtils.getRootCause(e));
         }
         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.
      * @return {@code true} if the entity has been already liked.
      */
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/resources/templates/likers.vm b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/resources/templates/likers.vm
index d6db7ce62ee95..34101f20590fd 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/resources/templates/likers.vm
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/main/resources/templates/likers.vm
@@ -15,21 +15,98 @@
 ## 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.
 ## ---------------------------------------------------------------------------
-#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
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/internal/DefaultLikeManagerTest.java b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/internal/DefaultLikeManagerTest.java
index e4a9685161394..2b9c9e6dcd95e 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/internal/DefaultLikeManagerTest.java
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/internal/DefaultLikeManagerTest.java
@@ -306,6 +306,16 @@ void getLikers() throws Exception
         when(this.ratingsManager.getRatings(queryMap, 12, 4, RatingsManager.RatingQueryField.UPDATED_DATE, false))
             .thenReturn(Arrays.asList(grading1, grading2, grading3));
         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));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/script/LikeScriptServiceTest.java b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/script/LikeScriptServiceTest.java
index 877d07219d9f0..a483dc76251b4 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/script/LikeScriptServiceTest.java
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-api/src/test/java/org/xwiki/like/script/LikeScriptServiceTest.java
@@ -18,10 +18,11 @@
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
 package org.xwiki.like.script;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 
 import javax.inject.Named;
 import javax.inject.Provider;
@@ -52,10 +53,11 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 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;
 
 /**
@@ -282,6 +284,24 @@ void cleanCache()
         // The Locale is important here since the AsyncRendererCache works with references containing a Locale.
         DocumentReference uixReference = new DocumentReference("foo", Arrays.asList("XWiki", "Like"), "LikeUIX",
             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));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.de.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.de.xml
index 376ed08cbd3f6..8307a6a2bf6b3 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.de.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.de.xml
@@ -55,7 +55,6 @@
 like.likers.title={0} Gefällt mir
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton="Gefällt mir" Schaltfläche immer anzeigen
 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>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.es.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.es.xml
index 8a4e41a063dc2..0f61f26889fba 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.es.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.es.xml
@@ -55,7 +55,6 @@
 like.likers.title="Me gusta" en {0}
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton=Mostrar siempre el botón "Me gusta"
 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>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.fr.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.fr.xml
index 52d7ba8f51723..66edc4df905b8 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.fr.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.fr.xml
@@ -55,7 +55,6 @@
 like.likers.title=J''aime sur {0}
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton=Toujours afficher le bouton J'aime
 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>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.no.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.no.xml
index c59fb6417cb19..fabde7fa3f3b4 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.no.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.no.xml
@@ -55,7 +55,6 @@
 like.likers.title=Liker på {0}
 XWiki.Like.LikeConfigurationClass_alwaysDisplayButton=Vis alltid Liker-knappen
 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>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.ru.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.ru.xml
index a142505e345a9..68ac9eca3b49c 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.ru.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.ru.xml
@@ -55,7 +55,6 @@
 ### Missing: like.likers.title=Likes on {0}
 ### Missing: XWiki.Like.LikeConfigurationClass_alwaysDisplayButton=Always display Like button
 ### 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>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.xml
index bfb7bfdb05667..f63d8650bd249 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeTranslations.xml
@@ -50,11 +50,14 @@
 like.button.title=Number of likes on this page: {0}
 like.button.title.like=Click to like the current page. Number of likes on this page: {0}.
 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
 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>
   <object>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeViewersMenuUIX.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeViewersMenuUIX.xml
index 4dfcf2e5eea2f..f9fbe179fcf50 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeViewersMenuUIX.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/LikeViewersMenuUIX.xml
@@ -181,6 +181,6 @@
     </property>
     <property>
       <scope>wiki</scope>
     </property>
   </object>
-</xwikidoc>
\ No newline at end of file
+</xwikidoc>
diff --git a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/UserProfileUIX.xml b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/UserProfileUIX.xml
index 8ebb111a62cd2..f6448d391499d 100644
--- a/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/UserProfileUIX.xml
+++ b/xwiki-platform-core/xwiki-platform-like/xwiki-platform-like-ui/src/main/resources/XWiki/Like/UserProfileUIX.xml
@@ -34,11 +34,100 @@
   <title/>
   <comment/>
   <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>
     <className>XWiki.UIExtensionClass</className>
     <guid>b79d80e9-7a28-4d56-b5d9-1de5cfa820ff</guid>
@@ -64,10 +153,12 @@
       </async_cached>
       <async_context>
         <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>
         <prettyName>Context elements</prettyName>
         <relationalStorage>0</relationalStorage>
@@ -152,25 +243,43 @@
     <property>
       <async_cached>0</async_cached>
     </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>
       <extensionPointId>org.xwiki.plaftorm.user.profile.menu</extensionPointId>
     </property>

Vulnerable code:                 <item>
                <item>
                <item>
import org.xwiki.component.manager.ComponentLifecycleException;
import org.xwiki.component.phase.Disposable;
public class DefaultLikeManager implements LikeManager, Initializable, Disposable
    @Override
    public void dispose() throws ComponentLifecycleException
    {
        //this.authorizationManager.
    }

#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')
      <p>
          $services.localization.render('like.likers.number', [$likers.size()])
      </p>
      <ul>
          #foreach($liker in $likers)
            <li>#displayUserLink($liker)</li>
          #end
      </ul>
</div>
  
  
  
  
  
like.likers.title=Likes on {0}
</xwikidoc>
  <content/>
        <value>wiki</value>
      <async_enabled>1</async_enabled>
#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
Fixed code:               <item>
              <item>
              <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>
    /**
     * 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;

     * @since 12.9RC1
    @Unstable
public class DefaultLikeManager implements LikeManager, Initializable
    @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);
        }
    }

    /**
     * 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;
    }

#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())
      #set ($totalRows = $likeRecords.size())
    {
        "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

    @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));
    }
import java.util.List;
import static org.mockito.Mockito.mock;

    @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));
    }
like.likers.title=Likers of {0}
like.livetable.column.title=Title
like.livetable.column.location=Location
like.livetable.column.likes=Likes
</xwikidoc>
  <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>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <value>doc.reference</value>
      <async_enabled>0</async_enabled>
=$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)