4

I'm building a travel site containing Campaigns (entries)

Each of these campaigns has multiple travel dates, modelled by a matrix field of

[startDate, endDate, price]

I want to do the following searches:

  • Campaigns with a travel startDate next month
  • Campaigns whose duration is more than 30 days and less than 60 days (endDate-startDate)
  • One of those criteria
  • All of the above criteria

How can this be achieved with twig template or custom controller?

PS: I looked into relations but that only applies to entries field, not matrix, and neither does it allow filtering on the related entity.

  • I'd seriously question the use of a matrix field for that sort of data - sounds like all of those values will be required for each entry and moving them to their own fields would make those searches a lot simpler. – Adam Gilleard Apr 8 '15 at 12:48
  • 1
    @Adam you're right, it is a lot simpler to filter entries by direct fields, but this shouldn't be a reason to decide against Matrix. You probably missed that the OP mentioned that each of these entries "campaigns" got multiple (maybe even optional) Matrix blocks "Travel dates". – carlcs Apr 8 '15 at 13:39
  • @carlcs you're right, I had missed the potential for multiple dates - apologies – Adam Gilleard Apr 8 '15 at 14:22
3

What I've done in a comparable situation is to add a hidden field to that entry type. And then I save Matrix data to that field on every save by listening to onBeforeSaveEntry events.

This makes the access to such processed Matrix data in templates or other parts of your plugin (i.e. getEntryTableAttributeHtml) very easy.

Event handler

craft()->on('entries.onBeforeSaveEntry', function(Event $event)
{
    $entry = $event->params['entry'];

    if($entry->section['handle'] == 'campaigns')
    {
        $longTravels = false;

        foreach ($entry->travelDates as $travelDate)
        {
            $duration = $travelDate->startDate->diff($travelDate->endDate);

            if ($duration > new DateInterval('P30D') && $duration < new DateInterval('P60D'))
            {
                $longTravels = true;
                break;
            }
        }

        // Set value for hidden field 'longTravels'
        $entry->getContent()->longTravels = $longTravels;
    }
});

CSS for hidden field type

{% set hiddenField %}
    #fields-{{ id }}-field {
        display: none;
    }
{% endset %}

{% includeCss hiddenField %}
| improve this answer | |
  • 2
    Along this route, you also might want to check out the Preparse Field plugin – it sure helped me fetch the relevant data that was stored within a matrix. – Avant la lettre Aug 22 '17 at 12:46
  • @Avantlalettre good suggestion, I think I wrote this answer before the Preparse plugin existed. Today, I use Preparse a lot for these kind of things. You might want to post your comment as a separate answer though, it’s a bit burried in all this text and code. I’d definitly upvote it (and accept, if it was my decision :D). – carlcs Aug 23 '17 at 6:27
2

Yeah you are probably better off putting the startDate, endDate, and price in non matrix fields. But if you don't something like this should probably work:

{# Get your list of entries (you probably don't want to start with all  entries, this is just an example #}
{% for entry in entries %}
    {# get the matrix field you want to sort things with #}
    {% for block in entry.yourTravelMatrix %}
        {# get the matrix block by type #}
        {% if block.type == "startDateBlock" %}
            {# set a variable to be the date in the field  #}
            {% set startDateVariable = block.startDateField %}
            {# use the variable to filter the entries #}
            {% if startDateVariable >= (now | date_modify("+1 months") %}
                {# whatever data you want to show from the entries #}
                {{ entry.title }}
            {% endif %}
        {% endif %}
    {% endfor %}
{% endfor %}

You'll probably have to adjust it a bit since dateTime fields are different than plain text fields. There may be a better way to filter by a Matrix field. Filtering by fields that aren't in a matrix is a lot easier. This should basically work though.

| improve this answer | |
  • Thanks @jason. We share the thought. However there's a small issue with this approach. I cannot paginate the result set, which is quite long. – thoaionline Apr 9 '15 at 4:47
1

I would like to propose something similar to this as a potential solution:

{# Matrix Search - Reasonably Elegant Solution #}

{# Initialise Matrix Block Owner IDs array #}
{% set matrixBlockOwnerIds = [] %}

{# This seems to be the easiest way to get the criteria in the shortest route #}
{% set ecm = craft.entries.section('mySectionHandle').type('myEntryTypeHandle').first().someMatrixBlockTypeHandle %}

{# Reset the owner ID because it was set by the first search #}
{% do ecm.setAttribute('ownerId', NULL) %}

{# Up the limit for a test - can be removed #}
{% do ecm.setAttribute('limit', 1000) %}

{# Set some search criteria #}
{% do ecm.setAttribute('startDate', '>= 2015-06-01') %}
{% do ecm.setAttribute('endDate', '<= 2015-07-01') %}

{# Find matching blocks #}
{% set matrixBlocks = ecm.find() %}

{# Iterate blocks and extract owner IDs (entry IDs) #}
{% for block in matrixBlocks %}
    {% set matrixBlockOwnerIds = matrixBlockOwnerIds|merge([block.ownerId]) %}
{% endfor %}

{# Query the entries by ID #}
{% set entries = craft.entries.find({'id': matrixBlockOwnerIds}) %}

{# Iterate entries #}
{% for entry in entries %}
    <h2>{{ entry.title }}</h2>
    {# Unfortunately we need to get the matrix blocks again, but this will include all blocks for the entry of that type #}
    {% for block in entry.someMatrixBlockTypeHandle.find() %}
        {# Output some block values #}
        <p>{{ block.valueA }} - {{ block.valueB }}</p>
    {% endfor %}
{% endfor %}

This isn't exact to your needs but I'm pretty sure it can be adjusted.

You can also do this for numeric ranges:

{% do ecm.setAttribute('someNumericValue', ['and', '> 10', '< 20') %}

If you need to combine AND and OR for the same field you'd have to repeat the search with the 'and' set to or, but not reset the matrixBlockOwnerIds array ..

e.g.

{% do ecm.setAttribute('someNumericValue', ['and', '> 10', '< 20') %}

{% set matrixBlocks = ecm.find() %}

{% for block in matrixBlocks %}
    {% set matrixBlockOwnerIds = matrixBlockOwnerIds|merge([block.ownerId]) %}
{% endfor %}

{% do ecm.setAttribute('someNumericValue', ['and', '> 10', '< 20') %}

{% set matrixBlocks = ecm.find() %}

{% for block in matrixBlocks %}
    {% set matrixBlockOwnerIds = matrixBlockOwnerIds|merge([block.ownerId]) %}
{% endfor %}

If you need any help with these examples, just give me a message.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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