2

I want to show entries (hundreds) for signed-in users randomly, I use this:

.order('RAND()')

and in the same time make sure to loop over all entries before start showing the entries that have been shown. Can you please help me?

2
  • Why do you want to loop over them before displaying them?
    – Brad Bell
    Commented Apr 1, 2023 at 23:24
  • My expression may not be clear. I want to display the entries. However, the point is that I want to make sure it doesn't re-display them (since it is randomly) before going through all the rest of them (entries that have been not displayed).
    – Mohamed
    Commented Apr 3, 2023 at 0:56

1 Answer 1

3

You need some way to keep track of which entries have been shown to the current user already, then exclude those entries on subsequent requests. One way to do this is using the session storage. However, this means that the list of entries already displayed will only persist for the current browser session. If you only need to provide this functionality for logged-in users, you can instead associate the list of entries with their account, thereby persisting it across sessions and devices.

This means you will need some kind of persistent storage. You could either write the list to a file or a custom database table, but for simplicity, I would just the cache storage.

{# 1 #}
{% set cache = craft.app.cache %}
{% set cacheKey = 'entries-already-shown-' ~ (currentUser.id ?? 0) %}
{% set alreadySeenIds = cache.get(cacheKey)|default([]) %}

{# 2 #}
{% set limit = 5 %}
{% set newRandomEntriesQuery = craft.entries()
    .section('my_section')
    .orderBy('RAND()')
    .limit(limit)
    .all()
%}
{% if alreadySeenIds %}
    {% do newRandomEntriesQuery.id(['not']|merge(alreadySeenIds)) %}
{% endif %}
{% set newRandomEntries = newRandomEntriesQuery.all() %}

{# 3 #}
{% set alreadySeenIds = alreadySeenIds|merge(newRandomEntries|column('id')) %}
{% do cache.set(cacheKey, alreadySeenIds, 31536000) %}

{# 4 #}
{% if newRandomEntries|length < limit %}
    {% do cache.delete(cacheKey) %}
{% endif %}

Let's look at that in more detail:

  1. We use the Cache component to get a list of entry IDs that were already shown to the current user. The cache key includes the current user's ID, so the cache will be unique to that user. The default is an empty array, if the user hasn't seen any entries yet.
  2. We get a list of new randomly selected entries (replace my_section with the section that the entries you want to show live in, and add any additional required query parameters). We exclude all the entries this user has already seen by their IDs.
  3. We update the list of entry IDs the user has already seen, merging the new ones with the existing list. The cache duration 31536000 is one year in seconds, but you can also choose a smaller value if you want to reset the list when a user hasn't viewed the site in a while.
  4. If there are fewer entries than the allowed limit, that means the user has seen all the available entries already. In this case, we reset the list of already seen entries.

There are some improvements that could be made to the code above. For example, when the user has seen almost all entries, the last request may show fewer (or even zero) entries. You could update step 4 to backfill the list from the full set of entries and start a new list of entries already seen in this case. To make handling those edge-cases easier, I suggest moving all that logic out of Twig and into PHP using a custom module. The module can expose a Twig function like getRandomEntriesForUser() that retrieves the next set of random entries using this logic. This also makes it easy to reuse that logic in different templates.

1
  • 1
    This is clever and detailed. Thank you very much for the accuracy and explanation in the answer. I appreciate your effort and time in writing this response. I will try this solution. Thanks again, MoritzLost.
    – Mohamed
    Commented Apr 3, 2023 at 1:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.