2

how would I go about setting up a route and twig template to filter by multiple tags?

Example URL scheme: example.com/tags/tag1/tag2/tag3

where tag1, tag2 and tag3 are tags defined in a tag field inside an entry. The resulting entries list should only output entries with tag1 AND tag2 AND tag3. tag2 and tag3 should be optional. I want to avoid the use of get params such as ?tag=tag1,tag2,tag3.

The ultimate goal is chain multiple tags to get finer results, similar to pinboard.in tag chaining.

Example:https://pinboard.in/t:javascript/t:css/t:webdev will return entries with javascript AND css AND webdev


My route looks as follows

URI: tags/tag-token/tag-taken2/tag-token3

and my twig template looks like

{% set entries = craft.entries.relatedTo(tag) %}

{% if craft.request.getSegment(2) !='' %}
<h1>Entries tagged with “{{ tag.title }}”</h1>
{% if entries | length %}
    <ul>
        {% for entry in entries %}
            <li>{{ entry.getLink() }}..</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No entries could be found with that tag.</p>
{% endif %}
{% endif %}


{% if craft.request.getSegment(3) !='' %}
{% set tag2entries = craft.entries.relatedTo('and',
    { element: entry, field: 'tags' },
    { element: entries, field: 'tags' }
) %}
{% set tag2entries = craft.entries.relatedTo(tag2-token) %}
<h1>Entries tagged with “{{ tag.title }} and {{ tag-token2}}”</h1>
{% if tag2entries | length %}
    <ul>
        {% for entry in tag2entries %}
            <li>{{ entry.getLink() }}..</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No entries could be found with those tags.</p>
{% endif %}
{% endif %}

{% if craft.request.getSegment(4) !='' %}
{% set tag3entries = craft.entries.relatedTo(tag3-token) %}
<h1>Entries tagged with “{{ tag.title }} and {{ tag-token2}} and {{tag-token3}}”</h1>
{% if tag3entries | length %}
    <ul>
        {% for entry in tag3entries %}
            <li>{{ entry.getLink() }}..</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No entries could be found with those tags.</p>
{% endif %}
{% endif %}

I'm am not sure how to go about using the relatedTo() function to filter entries based on those 3 tag parameters in the URL.

Any insight on how to go about this?

1 Answer 1

1

Ideally, you'll use a dynamic route that can match any number of tags – negating the need to hard code the number of possible tag segments.

You can't pull this off using the Routes admin inside the CP, so crack open your /craft/config/routes.php file and add this:

return array(
    'tags/(.+)' => 'path_to_your_tags_template',
); 

The above route can have any number of segments under /tags, but will require at least one. Be sure to delete any conflicting routes defined in Settings -> Routes!

Inside your tags template, you can get an array of all the tags in the URI like this (the |slice(1) part removes the first segment, i.e. "tags"):

{% set tagSegments = craft.request.segments|slice(1) %}

To pull the actual tags, you can do something like this:

{% set tags = craft.tags.slug(tagSegments) %}

Pulling it all together, here's how a (simplified) version of your template could look:

{# Get tags #}
{% set tagSegments = craft.request.segments|slice(1) %}
{% set tags = craft.tags.slug(tagSegments) %}

{# Get entries related to tags #}
{% set relatedTo = [] %}
{% for tagId in tags.ids() %}
    {% set relatedTo = relatedTo|merge([{
        targetElement: tagId,
        field: 'tags'
    }]) %}
{% endfor %}
{% if relatedTo|length > 1 %}
    {% set relatedTo = ['and']|merge(relatedTo) %}
{% endif %}
{% set entries = craft.entries.relatedTo(relatedTo) %}

{% if entries|length %}
    <h1>Entries tagged with {% for tag in tags %}{{ tag.title }}{% if not loop.last %}{{ loop.revindex == 2 ? ' and' : ',' }}{% endif %}{% endfor %}</h1>
    <ul>
        {% for entry in entries %}
            <li>{{ entry.getLink() }}..</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No entries could be found with {{ tagSegments|length > 1 ? 'those tags' : 'that tag' }}.</p>
{% endif %}
3
  • Thanks for your help! This (kinda) works. The problem is that it returns all posts that contain one of these tags. It should return posts that match all the tags. Its returning as if the tags are being OR'd and not ANDed which seems strange since 'and' is clearly being defined inside the relatedTo() function.
    – Markus
    Commented Dec 5, 2016 at 0:45
  • @Markus Yeah, that's a little strange. I've updated my answer with an alternative, more convoluted but possibly more robust solution. Another thing you could try is simply craft.entries.relatedTo('and', tags.ids()) (i.e. without joining the array of IDs). Sorry, don't have time to test either of these solutions right now. Commented Dec 5, 2016 at 6:41
  • Thank you! Your updated solution seems to work. I appreciate the help.
    – Markus
    Commented Dec 6, 2016 at 1:44

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.