Modify getResources to tvFilter after including resources

I’ve been stuck on this for a little while now. I just can’t seem to make any progress on it and wondered if anyone here had attempted or succeeded in achieving the same goal.

I have a site with products. The products have featured products within the same container as the other products that sit on top.

I have many sorting and filtering options, which is where I run into the issue. I can’t seem to work out how to make the TV filters also filter out the featured as the resources are added after the tvFilters take effect (inspecting the snippet).

After duplicating the snippet, I tried simply moving the resources function above the tvFilters on the off chance it would be that simple. It of course didn’t work, error below:

Got error ‘PHP message: PHP Fatal error: Uncaught Error: Cannot use object of type xPDOQueryCondition as array in /var/www/vhosts/xxxxxxx.com/xxxx-core/xpdo/om/xpdoquery.class.php:384\nStack trace:\n#0 /var/www/vhosts/xxxxxxx.com/xxxx-core/cache/includes/elements/modsnippet/81.include.cache.php(346): xPDOQuery->condition(Array, ‘(EXISTS (SELECT…’, ‘OR’, NULL, 0)\n#1 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/modx/modscript.class.php(76): include(’/var/www/vhosts…’)\n#2 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/modx/modparser.class.php(537): modScript->process(NULL)\n#3 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/modx/modparser.class.php(251): modParser->processTag(Array, true)\n#4 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/modx/modparser.class.php(431): modParser->processElementTags(’[[!getQuery:is=…’, ‘!getQuery:is=`f…’, true)\n#5 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/modx/modparser.class.php(251): modParser->processTag(Array, true)\n#6 /var/www/vhosts/xxxxxxx.com/xxxx-core/model/…’

Any help would be greatly appreciated.

And what exactly is your code now?

Apologies, please see below and thank you for coming back to me.

[[$top]]

<main class="artwork-listing bg-grey" data-barba="container" data-barba-namespace="artwork-listing" data-header-bg="bg-grey">
    [[$artwork-filters]]
    
    <div class="padded-section-full-width">
        <div class="artist-listing-toggle-container">
            <div class="artist-listing-toggle">
                <h3 class="artist-listing-toggle-title">View by:</h3>
                <div class="select-group">
                    <select id="artistListingToggle" dir="rtl">
                        <option value="[[~4]]">[[getResourceField? &id=`4` &field=`pagetitle`]]</option>
                        <option value="[[~803]]">[[getResourceField? &id=`803` &field=`pagetitle`]]</option>
                        <option value="[[~726]]" selected>Artwork</option>
                    </select>
                    
                    <span>&#9660;</span>
                </div>
            </div>
        </div>
        
        <div class="artist-listing-sorting-container">
            <div class="select-group">
                <select id="sortingToggle">
                    <option value="[[~726]]?sortby=featured&sortdir=DESC" [[!getURI:contains=`?sortby=featured&sortdir=DESC`:then=`selected`:else=``]]>Featured</option>
                    <option value="[[~726]]?sortby=createdon&sortdir=DESC" [[!getURI:contains=`?sortby=createdon&sortdir=DESC`:then=`selected`:else=``]]>Newest Listings First</option>
                    <option value="[[~726]]?sortby=createdon&sortdir=ASC" [[!getURI:contains=`?sortby=createdon&sortdir=ASC`:then=`selected`:else=``]]>Oldest Listing First</option>
                    <option value="[[~726]]?sortby=pagetitle&sortdir=ASC" [[!getURI:contains=`?sortby=pagetitle&sortdir=ASC`:then=`selected`:else=``]]>Artwork: A-Z</option>
                    <option value="[[~726]]?sortby=pagetitle&sortdir=DESC" [[!getURI:contains=`?sortby=pagetitle&sortdir=DESC`:then=`selected`:else=``]]>Artwork: Z-A</option>
                </select>
                
                <span>&#9660;</span>
            </div>
            
            <button class="artwork-filter-open"><span>Filter</span><img src="assets/img/Icons/filter-solid.svg"></button>
        </div>
        
        <section class="artwork-suggestion-container">
            <div class="artwork-suggestion-grid">
                [[!getQuery:is=`featured`:then=`
                    [[!getResourcesModified2?
                        &parents=`-1`
                        &limit=`0`
                        &includeTVs=`1`
                        &tvPrefix=``
                        &tpl=`artist-artwork.tpl`
                        &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                        &resources=`[[*featured-artwork]]`
                        &where=`{"published": "1"}`
                        &featured=`1`
                    ]]
                    [[!getResourcesModified2?
                        &parents=`726`
                        &limit=`0`
                        &includeTVs=`1`
                        &tvPrefix=``
                        &tpl=`artist-artwork.tpl`
                        &sortby=`{"createdon": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                        &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                        &resources=`-[[*featured-artwork:replace=`,==,-`]]`
                        &where=`{"published": "1"}`
                        &featured=`TEST
                    ]]
                `:else=`
                    [[!returnQueryString:contains=`sortby`:then=`
                        [[!getResourcesModified2?
                            &parents=`726`
                            &limit=`0`
                            &includeTVs=`1`
                            &tvPrefix=``
                            &tpl=`artist-artwork.tpl`
                            &sortby=`{"[[!getQuery:default=`createdon`? &query=`sortby`]]": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                            &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                            &toPlaceholder=`works`
                            &where=`{"published": "1"}`
                        ]]
                        
                        [[!+works:default=`
                            <div class="padded-section-full-width py-0">
                                <p>No search results found, please reduce the amount of active filters.</p>
                            </div>
                        `]]
                    `:else=`
                        [[!getResourcesModified2?
                            &parents=`-1`
                            &limit=`0`
                            &includeTVs=`1`
                            &tvPrefix=``
                            &tpl=`artist-artwork.tpl`
                            &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                            &resources=`[[*featured-artwork]]`
                            &where=`{"published": "1"}`
                            &featured=`1`
                        ]]
                        [[!getResourcesModified2?
                            &parents=`726`
                            &limit=`0`
                            &includeTVs=`1`
                            &tvPrefix=``
                            &tpl=`artist-artwork.tpl`
                            &sortby=`{"createdon": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                            &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                            &resources=`-[[*featured-artwork:replace=`,==,-`]]`
                            &where=`{"published": "1"}`
                            &featured=`1`
                        ]]
                    `]]
                    
                `? &query=`sortby`]]
            </div>
        </section>
    </div>
    
    [[$signup-section]]
</main>

[[$bottom]]

If you would like me to share some of the snippets or chunks as well, i’ll get them over.

I think, to see the code off getResourcesModified2 would be good

No problem, currently the only modification is the order in which TVs are processed:

<?php
/** Modifed 30.05.2019 - RN
 *  This has been modified so that tvFilters is grouped by AND operations first and
 *  then OR operations. This is not guaranteed to work in all cases and the standard getResources
 *  should be used instead unless you have a particular reason to use this version.
**/
/**
 * getResources
 *
 * A general purpose Resource listing and summarization snippet for MODX 2.x.
 *
 * @author Jason Coward
 * @copyright Copyright 2010-2013, Jason Coward
 *
 * TEMPLATES
 *
 * tpl - Name of a chunk serving as a resource template
 * [NOTE: if not provided, properties are dumped to output for each resource]
 *
 * tplOdd - (Opt) Name of a chunk serving as resource template for resources with an odd idx value
 * (see idx property)
 * tplFirst - (Opt) Name of a chunk serving as resource template for the first resource (see first
 * property)
 * tplLast - (Opt) Name of a chunk serving as resource template for the last resource (see last
 * property)
 * tpl_{n} - (Opt) Name of a chunk serving as resource template for the nth resource
 *
 * tplCondition - (Opt) Defines a field of the resource to evaluate against keys defined in the
 * conditionalTpls property. Must be a resource field; does not work with Template Variables.
 * conditionalTpls - (Opt) A JSON object defining a map of field values and the associated tpl to
 * use when the field defined by tplCondition matches the value. [NOTE: tplOdd, tplFirst, tplLast,
 * and tpl_{n} will take precedence over any defined conditionalTpls]
 *
 * tplWrapper - (Opt) Name of a chunk serving as a wrapper template for the output
 * [NOTE: Does not work with toSeparatePlaceholders]
 *
 * SELECTION
 *
 * parents - Comma-delimited list of ids serving as parents
 *
 * context - (Opt) Comma-delimited list of context keys to limit results by; if empty, contexts for all specified
 * parents will be used (all contexts if 0 is specified) [default=]
 *
 * depth - (Opt) Integer value indicating depth to search for resources from each parent [default=10]
 *
 * tvFilters - (Opt) Delimited-list of TemplateVar values to filter resources by. Supports two
 * delimiters and two value search formats. The first delimiter || represents a logical OR and the
 * primary grouping mechanism.  Within each group you can provide a comma-delimited list of values.
 * These values can be either tied to a specific TemplateVar by name, e.g. myTV==value, or just the
 * value, indicating you are searching for the value in any TemplateVar tied to the Resource. An
 * example would be &tvFilters=`filter2==one,filter1==bar%||filter1==foo`
 * [NOTE: filtering by values uses a LIKE query and % is considered a wildcard.]
 * [NOTE: this only looks at the raw value set for specific Resource, i. e. there must be a value
 * specifically set for the Resource and it is not evaluated.]
 *
 * tvFiltersAndDelimiter - (Opt) Custom delimiter for logical AND, default ',', in case you want to
 * match a literal comma in the tvFilters. E.g. &tvFiltersAndDelimiter=`&&`
 * &tvFilters=`filter1==foo,bar&&filter2==baz` [default=,]
 *
 * tvFiltersOrDelimiter - (Opt) Custom delimiter for logical OR, default '||', in case you want to
 * match a literal '||' in the tvFilters. E.g. &tvFiltersOrDelimiter=`|OR|`
 * &tvFilters=`filter1==foo||bar|OR|filter2==baz` [default=||]
 *
 * where - (Opt) A JSON expression of criteria to build any additional where clauses from. An example would be
 * &where=`{{"alias:LIKE":"foo%", "OR:alias:LIKE":"%bar"},{"OR:pagetitle:=":"foobar", "AND:description:=":"raboof"}}`
 *
 * sortby - (Opt) Field to sort by or a JSON array, e.g. {"publishedon":"ASC","createdon":"DESC"} [default=publishedon]
 * sortbyTV - (opt) A Template Variable name to sort by (if supplied, this precedes the sortby value) [default=]
 * sortbyTVType - (Opt) A data type to CAST a TV Value to in order to sort on it properly [default=string]
 * sortbyAlias - (Opt) Query alias for sortby field [default=]
 * sortbyEscaped - (Opt) Escapes the field name(s) specified in sortby [default=0]
 * sortdir - (Opt) Order which to sort by [default=DESC]
 * sortdirTV - (Opt) Order which to sort by a TV [default=DESC]
 * limit - (Opt) Limits the number of resources returned [default=5]
 * offset - (Opt) An offset of resources returned by the criteria to skip [default=0]
 * dbCacheFlag - (Opt) Controls caching of db queries; 0|false = do not cache result set; 1 = cache result set
 * according to cache settings, any other integer value = number of seconds to cache result set [default=0]
 *
 * OPTIONS
 *
 * includeContent - (Opt) Indicates if the content of each resource should be returned in the
 * results [default=0]
 * includeTVs - (Opt) Indicates if TemplateVar values should be included in the properties available
 * to each resource template [default=0]
 * includeTVList - (Opt) Limits the TemplateVars that are included if includeTVs is true to those specified
 * by name in a comma-delimited list [default=]
 * prepareTVs - (Opt) Prepares media-source dependent TemplateVar values [default=1]
 * prepareTVList - (Opt) Limits the TVs that are prepared to those specified by name in a comma-delimited
 * list [default=]
 * processTVs - (Opt) Indicates if TemplateVar values should be rendered as they would on the
 * resource being summarized [default=0]
 * processTVList - (opt) Limits the TemplateVars that are processed if included to those specified
 * by name in a comma-delimited list [default=]
 * tvPrefix - (Opt) The prefix for TemplateVar properties [default=tv.]
 * idx - (Opt) You can define the starting idx of the resources, which is an property that is
 * incremented as each resource is rendered [default=1]
 * first - (Opt) Define the idx which represents the first resource (see tplFirst) [default=1]
 * last - (Opt) Define the idx which represents the last resource (see tplLast) [default=# of
 * resources being summarized + first - 1]
 * outputSeparator - (Opt) An optional string to separate each tpl instance [default="\n"]
 * wrapIfEmpty - (Opt) Indicates if the tplWrapper should be applied if the output is empty [default=0]
 *
 */
$output = array();
$outputSeparator = isset($outputSeparator) ? $outputSeparator : "\n";

/* set default properties */
$tpl = !empty($tpl) ? $tpl : '';
$includeContent = !empty($includeContent) ? true : false;
$includeTVs = !empty($includeTVs) ? true : false;
$includeTVList = !empty($includeTVList) ? explode(',', $includeTVList) : array();
$processTVs = !empty($processTVs) ? true : false;
$processTVList = !empty($processTVList) ? explode(',', $processTVList) : array();
$prepareTVs = !empty($prepareTVs) ? true : false;
$prepareTVList = !empty($prepareTVList) ? explode(',', $prepareTVList) : array();
$tvPrefix = isset($tvPrefix) ? $tvPrefix : 'tv.';
$parents = (!empty($parents) || $parents === '0') ? explode(',', $parents) : array($modx->resource->get('id'));
array_walk($parents, 'trim');
$parents = array_unique($parents);
$depth = isset($depth) ? (integer) $depth : 10;

$tvFiltersOrDelimiter = isset($tvFiltersOrDelimiter) ? $tvFiltersOrDelimiter : '||';
$tvFiltersAndDelimiter = isset($tvFiltersAndDelimiter) ? $tvFiltersAndDelimiter : ',';
$tvFilters = !empty($tvFilters) ? explode($tvFiltersAndDelimiter, $tvFilters) : array();

$where = !empty($where) ? $modx->fromJSON($where) : array();
$showUnpublished = !empty($showUnpublished) ? true : false;
$showDeleted = !empty($showDeleted) ? true : false;

$sortby = isset($sortby) ? $sortby : 'publishedon';
$sortbyTV = isset($sortbyTV) ? $sortbyTV : '';
$sortbyAlias = isset($sortbyAlias) ? $sortbyAlias : 'modResource';
$sortbyEscaped = !empty($sortbyEscaped) ? true : false;
$sortdir = isset($sortdir) ? $sortdir : 'DESC';
$sortdirTV = isset($sortdirTV) ? $sortdirTV : 'DESC';
$limit = isset($limit) ? (integer) $limit : 5;
$offset = isset($offset) ? (integer) $offset : 0;
$totalVar = !empty($totalVar) ? $totalVar : 'total';

$dbCacheFlag = !isset($dbCacheFlag) ? false : $dbCacheFlag;
if (is_string($dbCacheFlag) || is_numeric($dbCacheFlag)) {
    if ($dbCacheFlag == '0') {
        $dbCacheFlag = false;
    } elseif ($dbCacheFlag == '1') {
        $dbCacheFlag = true;
    } else {
        $dbCacheFlag = (integer) $dbCacheFlag;
    }
}

/* multiple context support */
$contextArray = array();
$contextSpecified = false;
if (!empty($context)) {
    $contextArray = explode(',',$context);
    array_walk($contextArray, 'trim');
    $contexts = array();
    foreach ($contextArray as $ctx) {
        $contexts[] = $modx->quote($ctx);
    }
    $context = implode(',',$contexts);
    $contextSpecified = true;
    unset($contexts,$ctx);
} else {
    $context = $modx->quote($modx->context->get('key'));
}

$pcMap = array();
$pcQuery = $modx->newQuery('modResource', array('id:IN' => $parents), $dbCacheFlag);
$pcQuery->select(array('id', 'context_key'));
if ($pcQuery->prepare() && $pcQuery->stmt->execute()) {
    foreach ($pcQuery->stmt->fetchAll(PDO::FETCH_ASSOC) as $pcRow) {
        $pcMap[(integer) $pcRow['id']] = $pcRow['context_key'];
    }
}

$children = array();
$parentArray = array();
foreach ($parents as $parent) {
    $parent = (integer) $parent;
    if ($parent === 0) {
        $pchildren = array();
        if ($contextSpecified) {
            foreach ($contextArray as $pCtx) {
                if (!in_array($pCtx, $contextArray)) {
                    continue;
                }
                $options = $pCtx !== $modx->context->get('key') ? array('context' => $pCtx) : array();
                $pcchildren = $modx->getChildIds($parent, $depth, $options);
                if (!empty($pcchildren)) $pchildren = array_merge($pchildren, $pcchildren);
            }
        } else {
            $cQuery = $modx->newQuery('modContext', array('key:!=' => 'mgr'));
            $cQuery->select(array('key'));
            if ($cQuery->prepare() && $cQuery->stmt->execute()) {
                foreach ($cQuery->stmt->fetchAll(PDO::FETCH_COLUMN) as $pCtx) {
                    $options = $pCtx !== $modx->context->get('key') ? array('context' => $pCtx) : array();
                    $pcchildren = $modx->getChildIds($parent, $depth, $options);
                    if (!empty($pcchildren)) $pchildren = array_merge($pchildren, $pcchildren);
                }
            }
        }
        $parentArray[] = $parent;
    } else {
        $pContext = array_key_exists($parent, $pcMap) ? $pcMap[$parent] : false;
        if ($debug) $modx->log(modX::LOG_LEVEL_ERROR, "context for {$parent} is {$pContext}");
        if ($pContext && $contextSpecified && !in_array($pContext, $contextArray, true)) {
            $parent = next($parents);
            continue;
        }
        $parentArray[] = $parent;
        $options = !empty($pContext) && $pContext !== $modx->context->get('key') ? array('context' => $pContext) : array();
        $pchildren = $modx->getChildIds($parent, $depth, $options);
    }
    if (!empty($pchildren)) $children = array_merge($children, $pchildren);
    $parent = next($parents);
}
$parents = array_merge($parentArray, $children);

/* build query */
$criteria = array("modResource.parent IN (" . implode(',', $parents) . ")");
if ($contextSpecified) {
    $contextResourceTbl = $modx->getTableName('modContextResource');
    $criteria[] = "(modResource.context_key IN ({$context}) OR EXISTS(SELECT 1 FROM {$contextResourceTbl} ctx WHERE ctx.resource = modResource.id AND ctx.context_key IN ({$context})))";
}
if (empty($showDeleted)) {
    $criteria['deleted'] = '0';
}
if (empty($showUnpublished)) {
    $criteria['published'] = '1';
}
if (empty($showHidden)) {
    $criteria['hidemenu'] = '0';
}
if (!empty($hideContainers)) {
    $criteria['isfolder'] = '0';
}
$criteria = $modx->newQuery('modResource', $criteria);

if (!empty($tvFilters)) {
    $tmplVarTbl = $modx->getTableName('modTemplateVar');
    $tmplVarResourceTbl = $modx->getTableName('modTemplateVarResource');
    $conditions = array();
    $operators = array(
        '<=>' => '<=>',
        '===' => '=',
        '!==' => '!=',
        '<>' => '<>',
        '==' => 'LIKE',
        '!=' => 'NOT LIKE',
        '<<' => '<',
        '<=' => '<=',
        '=<' => '=<',
        '>>' => '>',
        '>=' => '>=',
        '=>' => '=>'
    );
    foreach ($tvFilters as $fGroup => $tvFilter) {
        $filterGroup = array();
        $filters = explode($tvFiltersOrDelimiter, $tvFilter);
        $multiple = count($filters) > 0;
        foreach ($filters as $filter) {
            $operator = '==';
            $sqlOperator = 'LIKE';
            foreach ($operators as $op => $opSymbol) {
                if (strpos($filter, $op, 1) !== false) {
                    $operator = $op;
                    $sqlOperator = $opSymbol;
                    break;
                }
            }
            $tvValueField = 'tvr.value';
            $tvDefaultField = 'tv.default_text';
            $f = explode($operator, $filter);
            if (count($f) >= 2) {
                if (count($f) > 2) {
                    $k = array_shift($f);
                    $b = join($operator, $f);
                    $f = array($k, $b);
                }
                $tvName = $modx->quote($f[0]);
                if (is_numeric($f[1]) && !in_array($sqlOperator, array('LIKE', 'NOT LIKE'))) {
                    $tvValue = $f[1];
                    if ($f[1] == (integer)$f[1]) {
                        $tvValueField = "CAST({$tvValueField} AS SIGNED INTEGER)";
                        $tvDefaultField = "CAST({$tvDefaultField} AS SIGNED INTEGER)";
                    } else {
                        $tvValueField = "CAST({$tvValueField} AS DECIMAL)";
                        $tvDefaultField = "CAST({$tvDefaultField} AS DECIMAL)";
                    }
                } else {
                    $tvValue = $modx->quote($f[1]);
                }
                if ($multiple) {
                    $filterGroup[] =
                        "(EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.name = {$tvName} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id) " .
                        "OR EXISTS (SELECT 1 FROM {$tmplVarTbl} tv WHERE tv.name = {$tvName} AND {$tvDefaultField} {$sqlOperator} {$tvValue} AND tv.id NOT IN (SELECT tmplvarid FROM {$tmplVarResourceTbl} WHERE contentid = modResource.id)) " .
                        ")";
                } else {
                    $filterGroup =
                        "(EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.name = {$tvName} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id) " .
                        "OR EXISTS (SELECT 1 FROM {$tmplVarTbl} tv WHERE tv.name = {$tvName} AND {$tvDefaultField} {$sqlOperator} {$tvValue} AND tv.id NOT IN (SELECT tmplvarid FROM {$tmplVarResourceTbl} WHERE contentid = modResource.id)) " .
                        ")";
                }
            } elseif (count($f) == 1) {
                $tvValue = $modx->quote($f[0]);
                if ($multiple) {
                    $filterGroup[] = "EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id)";
                } else {
                    $filterGroup = "EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id)";
                }
            }
        }
        $conditions[] = $filterGroup;
    }
    if (!empty($conditions)) {
        $firstGroup = true;
        foreach ($conditions as $cGroup => $c) {
            if (is_array($c)) {
                $first = true;
                foreach ($c as $cond) {
                    if ($first && !$firstGroup) {
                        $criteria->condition($criteria->query['where'][0][1], $cond, xPDOQuery::SQL_AND, null, $cGroup);
                    } else {
                        $criteria->condition($criteria->query['where'][0][1], $cond, xPDOQuery::SQL_OR, null, $cGroup);
                    }
                    $first = false;
                }
            } else {
                $criteria->condition($criteria->query['where'][0][1], $c, $firstGroup ? xPDOQuery::SQL_OR : xPDOQuery::SQL_AND, null, $cGroup);
            }
            $firstGroup = false;
        }
    }
}

/* include/exclude resources, via &resources=`123,-456` prop */
if (!empty($resources)) {
    $resourceConditions = array();
    $resources = explode(',',$resources);
    $include = array();
    $exclude = array();
    foreach ($resources as $resource) {
        $resource = (int)$resource;
        if ($resource == 0) continue;
        if ($resource < 0) {
            $exclude[] = abs($resource);
        } else {
            $include[] = $resource;
        }
    }
    if (!empty($include)) {
        $criteria->where(array('OR:modResource.id:IN' => $include), xPDOQuery::SQL_OR);
    }
    if (!empty($exclude)) {
        $criteria->where(array('modResource.id:NOT IN' => $exclude), xPDOQuery::SQL_AND, null, 1);
    }
}

if (!empty($where)) {
    $criteria->where($where);
}

$total = $modx->getCount('modResource', $criteria);
$modx->setPlaceholder($totalVar, $total);

$fields = array_keys($modx->getFields('modResource'));
if (empty($includeContent)) {
    $fields = array_diff($fields, array('content'));
}
$columns = $includeContent ? $modx->getSelectColumns('modResource', 'modResource') : $modx->getSelectColumns('modResource', 'modResource', '', array('content'), true);
$criteria->select($columns);
if (!empty($sortbyTV)) {
    $criteria->leftJoin('modTemplateVar', 'tvDefault', array(
        "tvDefault.name" => $sortbyTV
    ));
    $criteria->leftJoin('modTemplateVarResource', 'tvSort', array(
        "tvSort.contentid = modResource.id",
        "tvSort.tmplvarid = tvDefault.id"
    ));
    if (empty($sortbyTVType)) $sortbyTVType = 'string';
    if ($modx->getOption('dbtype') === 'mysql') {
        switch ($sortbyTVType) {
            case 'integer':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS SIGNED INTEGER) AS sortTV");
                break;
            case 'decimal':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS DECIMAL) AS sortTV");
                break;
            case 'datetime':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS DATETIME) AS sortTV");
                break;
            case 'string':
            default:
                $criteria->select("IFNULL(tvSort.value, tvDefault.default_text) AS sortTV");
                break;
        }
    } elseif ($modx->getOption('dbtype') === 'sqlsrv') {
        switch ($sortbyTVType) {
            case 'integer':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS BIGINT) AS sortTV");
                break;
            case 'decimal':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS DECIMAL) AS sortTV");
                break;
            case 'datetime':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS DATETIME) AS sortTV");
                break;
            case 'string':
            default:
                $criteria->select("ISNULL(tvSort.value, tvDefault.default_text) AS sortTV");
                break;
        }
    }
    $criteria->sortby("sortTV", $sortdirTV);
}
if (!empty($sortby)) {
    if (strpos($sortby, '{') === 0) {
        $sorts = $modx->fromJSON($sortby);
    } else {
        $sorts = array($sortby => $sortdir);
    }
    if (is_array($sorts)) {
        while (list($sort, $dir) = each($sorts)) {
            if ($sortbyEscaped) $sort = $modx->escape($sort);
            if (!empty($sortbyAlias)) $sort = $modx->escape($sortbyAlias) . ".{$sort}";
            $criteria->sortby($sort, $dir);
        }
    }
}
if (!empty($limit)) $criteria->limit($limit, $offset);

if (!empty($debug)) {
    $criteria->prepare();
    $modx->log(modX::LOG_LEVEL_ERROR, $criteria->toSQL());
}
$collection = $modx->getCollection('modResource', $criteria, $dbCacheFlag);

$idx = !empty($idx) || $idx === '0' ? (integer) $idx : 1;
$first = empty($first) && $first !== '0' ? 1 : (integer) $first;
$last = empty($last) ? (count($collection) + $idx - 1) : (integer) $last;

/* include parseTpl */
include_once $modx->getOption('getresources.core_path',null,$modx->getOption('core_path').'components/getresources/').'include.parsetpl.php';

$templateVars = array();
if (!empty($includeTVs) && !empty($includeTVList)) {
    $templateVars = $modx->getCollection('modTemplateVar', array('name:IN' => $includeTVList));
}
/** @var modResource $resource */
foreach ($collection as $resourceId => $resource) {
    $tvs = array();
    if (!empty($includeTVs)) {
        if (empty($includeTVList)) {
            $templateVars = $resource->getMany('TemplateVars');
        }
        /** @var modTemplateVar $templateVar */
        foreach ($templateVars as $tvId => $templateVar) {
            if (!empty($includeTVList) && !in_array($templateVar->get('name'), $includeTVList)) continue;
            if ($processTVs && (empty($processTVList) || in_array($templateVar->get('name'), $processTVList))) {
                $tvs[$tvPrefix . $templateVar->get('name')] = $templateVar->renderOutput($resource->get('id'));
            } else {
                $value = $templateVar->getValue($resource->get('id'));
                if ($prepareTVs && method_exists($templateVar, 'prepareOutput') && (empty($prepareTVList) || in_array($templateVar->get('name'), $prepareTVList))) {
                    $value = $templateVar->prepareOutput($value);
                }
                $tvs[$tvPrefix . $templateVar->get('name')] = $value;
            }
        }
    }
    $odd = ($idx & 1);
    $properties = array_merge(
        $scriptProperties
        ,array(
            'idx' => $idx
            ,'first' => $first
            ,'last' => $last
            ,'odd' => $odd
        )
        ,$includeContent ? $resource->toArray() : $resource->get($fields)
        ,$tvs
    );
    $resourceTpl = false;
    if ($idx == $first && !empty($tplFirst)) {
        $resourceTpl = parseTpl($tplFirst, $properties);
    }
    if ($idx == $last && empty($resourceTpl) && !empty($tplLast)) {
        $resourceTpl = parseTpl($tplLast, $properties);
    }
    $tplidx = 'tpl_' . $idx;
    if (empty($resourceTpl) && !empty($$tplidx)) {
        $resourceTpl = parseTpl($$tplidx, $properties);
    }
    if ($idx > 1 && empty($resourceTpl)) {
        $divisors = getDivisors($idx);
        if (!empty($divisors)) {
            foreach ($divisors as $divisor) {
                $tplnth = 'tpl_n' . $divisor;
                if (!empty($$tplnth)) {
                    $resourceTpl = parseTpl($$tplnth, $properties);
                    if (!empty($resourceTpl)) {
                        break;
                    }
                }
            }
        }
    }
    if ($odd && empty($resourceTpl) && !empty($tplOdd)) {
        $resourceTpl = parseTpl($tplOdd, $properties);
    }
    if (!empty($tplCondition) && !empty($conditionalTpls) && empty($resourceTpl)) {
        $conTpls = $modx->fromJSON($conditionalTpls);
        $subject = $properties[$tplCondition];
        $tplOperator = !empty($tplOperator) ? $tplOperator : '=';
        $tplOperator = strtolower($tplOperator);
        $tplCon = '';
        foreach ($conTpls as $operand => $conditionalTpl) {
            switch ($tplOperator) {
                case '!=':
                case 'neq':
                case 'not':
                case 'isnot':
                case 'isnt':
                case 'unequal':
                case 'notequal':
                    $tplCon = (($subject != $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '<':
                case 'lt':
                case 'less':
                case 'lessthan':
                    $tplCon = (($subject < $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '>':
                case 'gt':
                case 'greater':
                case 'greaterthan':
                    $tplCon = (($subject > $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '<=':
                case 'lte':
                case 'lessthanequals':
                case 'lessthanorequalto':
                    $tplCon = (($subject <= $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '>=':
                case 'gte':
                case 'greaterthanequals':
                case 'greaterthanequalto':
                    $tplCon = (($subject >= $operand) ? $conditionalTpl : $tplCon);
                    break;
                case 'isempty':
                case 'empty':
                    $tplCon = empty($subject) ? $conditionalTpl : $tplCon;
                    break;
                case '!empty':
                case 'notempty':
                case 'isnotempty':
                    $tplCon = !empty($subject) && $subject != '' ? $conditionalTpl : $tplCon;
                    break;
                case 'isnull':
                case 'null':
                    $tplCon = $subject == null || strtolower($subject) == 'null' ? $conditionalTpl : $tplCon;
                    break;
                case 'inarray':
                case 'in_array':
                case 'ia':
                    $operand = explode(',', $operand);
                    $tplCon = in_array($subject, $operand) ? $conditionalTpl : $tplCon;
                    break;
                case 'between':
                case 'range':
                case '>=<':
                case '><':
                    $operand = explode(',', $operand);
                    $tplCon = ($subject >= min($operand) && $subject <= max($operand)) ? $conditionalTpl : $tplCon;
                    break;
                case '==':
                case '=':
                case 'eq':
                case 'is':
                case 'equal':
                case 'equals':
                case 'equalto':
                default:
                    $tplCon = (($subject == $operand) ? $conditionalTpl : $tplCon);
                    break;
            }
        }
        if (!empty($tplCon)) {
            $resourceTpl = parseTpl($tplCon, $properties);
        }
    }
    if (!empty($tpl) && empty($resourceTpl)) {
        $resourceTpl = parseTpl($tpl, $properties);
    }
    if ($resourceTpl === false && !empty($debug)) {
        $chunk = $modx->newObject('modChunk');
        $chunk->setCacheable(false);
        $output[]= $chunk->process(array(), '<pre>' . print_r($properties, true) .'</pre>');
    } else {
        $output[]= $resourceTpl;
    }
    $idx++;
}

/* output */
$toSeparatePlaceholders = $modx->getOption('toSeparatePlaceholders', $scriptProperties, false);
if (!empty($toSeparatePlaceholders)) {
    $modx->setPlaceholders($output, $toSeparatePlaceholders);
    return '';
}

$output = implode($outputSeparator, $output);

$tplWrapper = $modx->getOption('tplWrapper', $scriptProperties, false);
$wrapIfEmpty = $modx->getOption('wrapIfEmpty', $scriptProperties, false);
if (!empty($tplWrapper) && ($wrapIfEmpty || !empty($output))) {
    $output = parseTpl($tplWrapper, array_merge($scriptProperties, array('output' => $output)));
}

$toPlaceholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
if (!empty($toPlaceholder)) {
    $modx->setPlaceholder($toPlaceholder, $output);
    return '';
}
return $output;

And getQuery too, maybe

getQuery:

<?php
return $_GET[$query];

tvFiltersArray:

<?php

if(isset($array) && isset($tvname) && isset($delimiter) && isset($operator)) {
    if(strpos($array, $delimiter)) {
        $output;
        
        $array = explode($delimiter, $array);
        
        foreach($array as $value) {
            $snippetArray = array_merge(json_decode($runSnippetParams, true), array('id' => $value));
            
            $processedValue = $modx->runSnippet($runSnippet, $snippetArray);
            
            $output .= $tvname.$operator.$processedValue.'||';
        }
        
        return rtrim($output, '||');
    } else {
        $snippetArray = array_merge(json_decode($runSnippetParams, true), array('id' => $array));
        
        $processedValue = $modx->runSnippet($runSnippet, $snippetArray);
        
        return $tvname.$operator.$processedValue;
    }
} else {
    return '&array=``, &tvname=``, &delimiter=``, &operator=``, &runSnippet=``, &runSnippetParams=`` must have a value';
}

tvFiltersGetQueryArray:

<?php
if(isset($query) && isset($tvname) && isset($delimiter) && isset($operator)) {
    if(strpos($_GET[$query], $delimiter)) {
        $output;
    
        $array = explode($delimiter, $_GET[$query]);
        
        foreach($array as $value) {
            $output .= $tvname.$operator.$value.'||';
        }
        
        return rtrim($output, '||');
    } else {
        return $tvname.$operator.$_GET[$query];
    }
} else {
    return '&query=``, &tvname=``, &delimiter=``, &operator=`` must have a value';
}

I’m struggling to understand what exactly you are trying to do.

I suppose it’s the combination of the properties &tvFilters and &resources that doesn’t give you the right result. Could you explain in more detail what doesn’t work.

And maybe with some example values for these properties (as it is really hard to discern what happens in your code with all these nested tags). Something like this:

[[!getResourcesModified2?
	...
	&tvFilters=`artwork-genre==somegenre,artwork-color==blue||artwork-color==red`
	&resources=`11,21,31`
]]

[[!getResourcesModified2?
	...
	&tvFilters=`artwork-genre==somegenre,artwork-color==blue||artwork-color==red`
	&resources=`-11,-21,-31`
]]

BTW. From what I can tell, your code is super inefficient. For example this part

[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]

that generates the value for &tvFilters is executed 5 times to generate the same value (as far as I can tell). Putting all that functionality into 1 snippet could avoid that and also make your code more readable.

I think, I would put all the conditional logic into one snippet and call getResourcesModified2 within that snippet with $modx->runSnippet('getResourcesModified2,$properties').

Thanks guys, I’ll have a play.

The only thing is, the problem I’m having is that the resources are added after the tvFilters.

I need to also filter the resources that are included in the &resources=``

If I understand you correctly, then the problem is that when you include certain resources (e.g. &resources=`11,21,31` ) these resources should also only display if they meet the tvFilters criteria.

If you include the property &debug=`1` in your call to “getResourcesModified2” then the SQL query gets written to the error log (which can help identify the problem).

When I run

[[!getResourcesModified2?
    &parents=`726`
    &limit=`0`
    &includeTVs=`1`
    &tvPrefix=``
    &tpl=`artist-artwork.tpl`
    &tvFilters=`artwork-genre==somegenre,artwork-color==blue||artwork-color==red`
    &resources=`11,21,31`
    &debug=`1`
]]

I get an SQL query like this: (simplifed)

SELECT *
FROM `modx_site_content` AS `modResource` WHERE  (  
	( 	modResource.parent IN (726) 
		AND `modResource`.`deleted` = 0 
		AND `modResource`.`published` = 1 
		AND `modResource`.`hidemenu` = 0 
	)  
	AND  ( -- the tvFilters stuff
		(EXISTS () OR EXISTS (...) ) 
		AND ( (EXISTS (...) OR EXISTS (...) ) OR (EXISTS (...) OR EXISTS (...) ) )  
	)  
	OR `modResource`.`id` IN (11,21,31) 
)  ORDER BY `modResource`.publishedon DESC 

I suppose you want the OR in OR `modResource`.`id` IN (11,21,31) to be an AND?


So you probably have to change this line in your “getResourcesModified2” snippet

$criteria->where(array('OR:modResource.id:IN' => $include), xPDOQuery::SQL_OR);

to maybe

$criteria->where(array('AND:modResource.id:IN' => $include), xPDOQuery::SQL_OR);

Another question: Is there a reason why you call “getResourcesModified2” twice or is it just to make sure that the featured items are at the top?

[[!getResourcesModified2?
	...
	&tvFilters=`artwork-genre==somegenre,artwork-color==blue||artwork-color==red`
	&resources=`11,21,31`
]]

[[!getResourcesModified2?
	...
	&tvFilters=`artwork-genre==somegenre,artwork-color==blue||artwork-color==red`
	&resources=`-11,-21,-31`
]]

If it’s just a way to sort the items, then it’s probably easier (and faster) to make only one call to “getResourcesModified2” and then look for featured items in the $output-array (in the snippet code) and reorder the array before it is converted to a string ($output = implode($outputSeparator, $output);)

Thank you this is very helpful. I will be back on to this task shortly, so should be able to come back to you soon.

In relation to the second question, yes you are correct. I will definitely make this change.

This was one of them builds that the initial spec changes so much after it’s already been built and with such a tight deadline, you end up with mess of a code that needs a lot of work post live.

Thanks again, I’ll come back later today with a resolve.

Thank you both for the help, your guidance made me rethink and come up with a solution. I ended up dropping the “&resources=xxx” field and adding “&featuredResources=xxx”. This also allowed me to remove the two calls to the getResources snippet.

<div class="artwork-suggestion-grid">
                [[!getQuery:is=`featured`:then=`
                    [[!getResourcesModified2?
                        &parents=`726`
                        &limit=`0`
                        &includeTVs=`1`
                        &tvPrefix=``
                        &tpl=`artist-artwork.tpl`
                        &sortby=`{"createdon": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                        &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                        &where=`{"published": "1"}`
                        &featuredResources=`[[*featured-artwork]]`
                        &toPlaceholder=`works`
                    ]]
                        
                    [[!+works:default=`
                        <div class="padded-section-full-width py-0">
                            <p>No search results found, please reduce the amount of active filters.</p>
                        </div>
                    `]]
                `:else=`
                    [[!returnQueryString:contains=`sortby`:then=`
                        [[!getResourcesModified2?
                            &parents=`726`
                            &limit=`0`
                            &includeTVs=`1`
                            &tvPrefix=``
                            &tpl=`artist-artwork.tpl`
                            &sortby=`{"[[!getQuery:default=`createdon`? &query=`sortby`]]": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                            &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                            &toPlaceholder=`works`
                            &where=`{"published": "1"}`
                            &debug=`1`
                        ]]
                        
                        [[!+works:default=`
                            <div class="padded-section-full-width py-0">
                                <p>No search results found, please reduce the amount of active filters.</p>
                            </div>
                        `]]
                    `:else=`
                        [[!getResourcesModified2?
                            &parents=`726`
                            &limit=`0`
                            &includeTVs=`1`
                            &tvPrefix=``
                            &tpl=`artist-artwork.tpl`
                            &sortby=`{"createdon": "[[!getQuery:default=`DESC`? &query=`sortdir`]]"}`
                            &tvFilters=`[[!stripTrailingComma? &input=`[[!getQuery:notempty=`[[!tvFiltersArray? &array=`[[!getQuery? &query=`genre`]]` &tvname=`artwork-genre` &delimiter=`,` &operator=`==` &runSnippet=`getResourceField` &runSnippetParams=`{"field": "pagetitle"}`]],`? &query=`genre`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`colour` &tvname=`artwork-color` &delimiter=`,` &operator=`==`]],`? &query=`colour`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`style` &tvname=`artwork-style` &delimiter=`,` &operator=`==`]],`? &query=`style`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`medium` &tvname=`artwork-medium` &delimiter=`,` &operator=`==`]],`? &query=`medium`]][[!getQuery:notempty=`[[!tvFiltersGetQueryArray? &query=`type` &tvname=`artwork-type` &delimiter=`,` &operator=`==`]],`? &query=`type`]]`]]`
                            &where=`{"published": "1"}`
                            &featuredResources=`[[*featured-artwork]]`
                            &toPlaceholder=`works`
                        ]]
                            
                        [[!+works:default=`
                            <div class="padded-section-full-width py-0">
                                <p>No search results found, please reduce the amount of active filters.</p>
                            </div>
                        `]]
                    `]]
                    
                `? &query=`sortby`]]
            </div>

I then simply moved any matches to the front of the output.

if(!empty($featuredResources)) {
    $tmp = array();
    $featuredResources = explode(',', $featuredResources);
    
    foreach($collection as $resourceId => $resource) {
        if(in_array($resource->id, $featuredResources)) {
            $tmp[$resourceId] = $resource;
            unset($collection[$resourceId]);
        }
    }
    
    $collection = array_merge($tmp, $collection);
}

I will add the full snippet below for future reference, it may help someone else.

<?php
/** Modifed 30.05.2019 - RN
 *  This has been modified so that tvFilters is grouped by AND operations first and
 *  then OR operations. This is not guaranteed to work in all cases and the standard getResources
 *  should be used instead unless you have a particular reason to use this version.
**/
/**
 * getResources
 *
 * A general purpose Resource listing and summarization snippet for MODX 2.x.
 *
 * @author Jason Coward
 * @copyright Copyright 2010-2013, Jason Coward
 *
 * TEMPLATES
 *
 * tpl - Name of a chunk serving as a resource template
 * [NOTE: if not provided, properties are dumped to output for each resource]
 *
 * tplOdd - (Opt) Name of a chunk serving as resource template for resources with an odd idx value
 * (see idx property)
 * tplFirst - (Opt) Name of a chunk serving as resource template for the first resource (see first
 * property)
 * tplLast - (Opt) Name of a chunk serving as resource template for the last resource (see last
 * property)
 * tpl_{n} - (Opt) Name of a chunk serving as resource template for the nth resource
 *
 * tplCondition - (Opt) Defines a field of the resource to evaluate against keys defined in the
 * conditionalTpls property. Must be a resource field; does not work with Template Variables.
 * conditionalTpls - (Opt) A JSON object defining a map of field values and the associated tpl to
 * use when the field defined by tplCondition matches the value. [NOTE: tplOdd, tplFirst, tplLast,
 * and tpl_{n} will take precedence over any defined conditionalTpls]
 *
 * tplWrapper - (Opt) Name of a chunk serving as a wrapper template for the output
 * [NOTE: Does not work with toSeparatePlaceholders]
 *
 * SELECTION
 *
 * parents - Comma-delimited list of ids serving as parents
 *
 * context - (Opt) Comma-delimited list of context keys to limit results by; if empty, contexts for all specified
 * parents will be used (all contexts if 0 is specified) [default=]
 *
 * depth - (Opt) Integer value indicating depth to search for resources from each parent [default=10]
 *
 * tvFilters - (Opt) Delimited-list of TemplateVar values to filter resources by. Supports two
 * delimiters and two value search formats. The first delimiter || represents a logical OR and the
 * primary grouping mechanism.  Within each group you can provide a comma-delimited list of values.
 * These values can be either tied to a specific TemplateVar by name, e.g. myTV==value, or just the
 * value, indicating you are searching for the value in any TemplateVar tied to the Resource. An
 * example would be &tvFilters=`filter2==one,filter1==bar%||filter1==foo`
 * [NOTE: filtering by values uses a LIKE query and % is considered a wildcard.]
 * [NOTE: this only looks at the raw value set for specific Resource, i. e. there must be a value
 * specifically set for the Resource and it is not evaluated.]
 *
 * tvFiltersAndDelimiter - (Opt) Custom delimiter for logical AND, default ',', in case you want to
 * match a literal comma in the tvFilters. E.g. &tvFiltersAndDelimiter=`&&`
 * &tvFilters=`filter1==foo,bar&&filter2==baz` [default=,]
 *
 * tvFiltersOrDelimiter - (Opt) Custom delimiter for logical OR, default '||', in case you want to
 * match a literal '||' in the tvFilters. E.g. &tvFiltersOrDelimiter=`|OR|`
 * &tvFilters=`filter1==foo||bar|OR|filter2==baz` [default=||]
 *
 * where - (Opt) A JSON expression of criteria to build any additional where clauses from. An example would be
 * &where=`{{"alias:LIKE":"foo%", "OR:alias:LIKE":"%bar"},{"OR:pagetitle:=":"foobar", "AND:description:=":"raboof"}}`
 *
 * sortby - (Opt) Field to sort by or a JSON array, e.g. {"publishedon":"ASC","createdon":"DESC"} [default=publishedon]
 * sortbyTV - (opt) A Template Variable name to sort by (if supplied, this precedes the sortby value) [default=]
 * sortbyTVType - (Opt) A data type to CAST a TV Value to in order to sort on it properly [default=string]
 * sortbyAlias - (Opt) Query alias for sortby field [default=]
 * sortbyEscaped - (Opt) Escapes the field name(s) specified in sortby [default=0]
 * sortdir - (Opt) Order which to sort by [default=DESC]
 * sortdirTV - (Opt) Order which to sort by a TV [default=DESC]
 * limit - (Opt) Limits the number of resources returned [default=5]
 * offset - (Opt) An offset of resources returned by the criteria to skip [default=0]
 * dbCacheFlag - (Opt) Controls caching of db queries; 0|false = do not cache result set; 1 = cache result set
 * according to cache settings, any other integer value = number of seconds to cache result set [default=0]
 *
 * OPTIONS
 *
 * includeContent - (Opt) Indicates if the content of each resource should be returned in the
 * results [default=0]
 * includeTVs - (Opt) Indicates if TemplateVar values should be included in the properties available
 * to each resource template [default=0]
 * includeTVList - (Opt) Limits the TemplateVars that are included if includeTVs is true to those specified
 * by name in a comma-delimited list [default=]
 * prepareTVs - (Opt) Prepares media-source dependent TemplateVar values [default=1]
 * prepareTVList - (Opt) Limits the TVs that are prepared to those specified by name in a comma-delimited
 * list [default=]
 * processTVs - (Opt) Indicates if TemplateVar values should be rendered as they would on the
 * resource being summarized [default=0]
 * processTVList - (opt) Limits the TemplateVars that are processed if included to those specified
 * by name in a comma-delimited list [default=]
 * tvPrefix - (Opt) The prefix for TemplateVar properties [default=tv.]
 * idx - (Opt) You can define the starting idx of the resources, which is an property that is
 * incremented as each resource is rendered [default=1]
 * first - (Opt) Define the idx which represents the first resource (see tplFirst) [default=1]
 * last - (Opt) Define the idx which represents the last resource (see tplLast) [default=# of
 * resources being summarized + first - 1]
 * outputSeparator - (Opt) An optional string to separate each tpl instance [default="\n"]
 * wrapIfEmpty - (Opt) Indicates if the tplWrapper should be applied if the output is empty [default=0]
 *
 */
$output = array();
$outputSeparator = isset($outputSeparator) ? $outputSeparator : "\n";

/* set default properties */
$tpl = !empty($tpl) ? $tpl : '';
$includeContent = !empty($includeContent) ? true : false;
$includeTVs = !empty($includeTVs) ? true : false;
$includeTVList = !empty($includeTVList) ? explode(',', $includeTVList) : array();
$processTVs = !empty($processTVs) ? true : false;
$processTVList = !empty($processTVList) ? explode(',', $processTVList) : array();
$prepareTVs = !empty($prepareTVs) ? true : false;
$prepareTVList = !empty($prepareTVList) ? explode(',', $prepareTVList) : array();
$tvPrefix = isset($tvPrefix) ? $tvPrefix : 'tv.';
$parents = (!empty($parents) || $parents === '0') ? explode(',', $parents) : array($modx->resource->get('id'));
array_walk($parents, 'trim');
$parents = array_unique($parents);
$depth = isset($depth) ? (integer) $depth : 10;

$tvFiltersOrDelimiter = isset($tvFiltersOrDelimiter) ? $tvFiltersOrDelimiter : '||';
$tvFiltersAndDelimiter = isset($tvFiltersAndDelimiter) ? $tvFiltersAndDelimiter : ',';
$tvFilters = !empty($tvFilters) ? explode($tvFiltersAndDelimiter, $tvFilters) : array();

$where = !empty($where) ? $modx->fromJSON($where) : array();
$showUnpublished = !empty($showUnpublished) ? true : false;
$showDeleted = !empty($showDeleted) ? true : false;

$sortby = isset($sortby) ? $sortby : 'publishedon';
$sortbyTV = isset($sortbyTV) ? $sortbyTV : '';
$sortbyAlias = isset($sortbyAlias) ? $sortbyAlias : 'modResource';
$sortbyEscaped = !empty($sortbyEscaped) ? true : false;
$sortdir = isset($sortdir) ? $sortdir : 'DESC';
$sortdirTV = isset($sortdirTV) ? $sortdirTV : 'DESC';
$limit = isset($limit) ? (integer) $limit : 5;
$offset = isset($offset) ? (integer) $offset : 0;
$totalVar = !empty($totalVar) ? $totalVar : 'total';

$dbCacheFlag = !isset($dbCacheFlag) ? false : $dbCacheFlag;
if (is_string($dbCacheFlag) || is_numeric($dbCacheFlag)) {
    if ($dbCacheFlag == '0') {
        $dbCacheFlag = false;
    } elseif ($dbCacheFlag == '1') {
        $dbCacheFlag = true;
    } else {
        $dbCacheFlag = (integer) $dbCacheFlag;
    }
}

/* multiple context support */
$contextArray = array();
$contextSpecified = false;
if (!empty($context)) {
    $contextArray = explode(',',$context);
    array_walk($contextArray, 'trim');
    $contexts = array();
    foreach ($contextArray as $ctx) {
        $contexts[] = $modx->quote($ctx);
    }
    $context = implode(',',$contexts);
    $contextSpecified = true;
    unset($contexts,$ctx);
} else {
    $context = $modx->quote($modx->context->get('key'));
}

$pcMap = array();
$pcQuery = $modx->newQuery('modResource', array('id:IN' => $parents), $dbCacheFlag);
$pcQuery->select(array('id', 'context_key'));
if ($pcQuery->prepare() && $pcQuery->stmt->execute()) {
    foreach ($pcQuery->stmt->fetchAll(PDO::FETCH_ASSOC) as $pcRow) {
        $pcMap[(integer) $pcRow['id']] = $pcRow['context_key'];
    }
}

$children = array();
$parentArray = array();
foreach ($parents as $parent) {
    $parent = (integer) $parent;
    if ($parent === 0) {
        $pchildren = array();
        if ($contextSpecified) {
            foreach ($contextArray as $pCtx) {
                if (!in_array($pCtx, $contextArray)) {
                    continue;
                }
                $options = $pCtx !== $modx->context->get('key') ? array('context' => $pCtx) : array();
                $pcchildren = $modx->getChildIds($parent, $depth, $options);
                if (!empty($pcchildren)) $pchildren = array_merge($pchildren, $pcchildren);
            }
        } else {
            $cQuery = $modx->newQuery('modContext', array('key:!=' => 'mgr'));
            $cQuery->select(array('key'));
            if ($cQuery->prepare() && $cQuery->stmt->execute()) {
                foreach ($cQuery->stmt->fetchAll(PDO::FETCH_COLUMN) as $pCtx) {
                    $options = $pCtx !== $modx->context->get('key') ? array('context' => $pCtx) : array();
                    $pcchildren = $modx->getChildIds($parent, $depth, $options);
                    if (!empty($pcchildren)) $pchildren = array_merge($pchildren, $pcchildren);
                }
            }
        }
        $parentArray[] = $parent;
    } else {
        $pContext = array_key_exists($parent, $pcMap) ? $pcMap[$parent] : false;
        if ($debug) $modx->log(modX::LOG_LEVEL_ERROR, "context for {$parent} is {$pContext}");
        if ($pContext && $contextSpecified && !in_array($pContext, $contextArray, true)) {
            $parent = next($parents);
            continue;
        }
        $parentArray[] = $parent;
        $options = !empty($pContext) && $pContext !== $modx->context->get('key') ? array('context' => $pContext) : array();
        $pchildren = $modx->getChildIds($parent, $depth, $options);
    }
    if (!empty($pchildren)) $children = array_merge($children, $pchildren);
    $parent = next($parents);
}
$parents = array_merge($parentArray, $children);

/* build query */
$criteria = array("modResource.parent IN (" . implode(',', $parents) . ")");
if ($contextSpecified) {
    $contextResourceTbl = $modx->getTableName('modContextResource');
    $criteria[] = "(modResource.context_key IN ({$context}) OR EXISTS(SELECT 1 FROM {$contextResourceTbl} ctx WHERE ctx.resource = modResource.id AND ctx.context_key IN ({$context})))";
}
if (empty($showDeleted)) {
    $criteria['deleted'] = '0';
}
if (empty($showUnpublished)) {
    $criteria['published'] = '1';
}
if (empty($showHidden)) {
    $criteria['hidemenu'] = '0';
}
if (!empty($hideContainers)) {
    $criteria['isfolder'] = '0';
}
$criteria = $modx->newQuery('modResource', $criteria);

if (!empty($tvFilters)) {
    $tmplVarTbl = $modx->getTableName('modTemplateVar');
    $tmplVarResourceTbl = $modx->getTableName('modTemplateVarResource');
    $conditions = array();
    $operators = array(
        '<=>' => '<=>',
        '===' => '=',
        '!==' => '!=',
        '<>' => '<>',
        '==' => 'LIKE',
        '!=' => 'NOT LIKE',
        '<<' => '<',
        '<=' => '<=',
        '=<' => '=<',
        '>>' => '>',
        '>=' => '>=',
        '=>' => '=>'
    );
    foreach ($tvFilters as $fGroup => $tvFilter) {
        $filterGroup = array();
        $filters = explode($tvFiltersOrDelimiter, $tvFilter);
        $multiple = count($filters) > 0;
        foreach ($filters as $filter) {
            $operator = '==';
            $sqlOperator = 'LIKE';
            foreach ($operators as $op => $opSymbol) {
                if (strpos($filter, $op, 1) !== false) {
                    $operator = $op;
                    $sqlOperator = $opSymbol;
                    break;
                }
            }
            $tvValueField = 'tvr.value';
            $tvDefaultField = 'tv.default_text';
            $f = explode($operator, $filter);
            if (count($f) >= 2) {
                if (count($f) > 2) {
                    $k = array_shift($f);
                    $b = join($operator, $f);
                    $f = array($k, $b);
                }
                $tvName = $modx->quote($f[0]);
                if (is_numeric($f[1]) && !in_array($sqlOperator, array('LIKE', 'NOT LIKE'))) {
                    $tvValue = $f[1];
                    if ($f[1] == (integer)$f[1]) {
                        $tvValueField = "CAST({$tvValueField} AS SIGNED INTEGER)";
                        $tvDefaultField = "CAST({$tvDefaultField} AS SIGNED INTEGER)";
                    } else {
                        $tvValueField = "CAST({$tvValueField} AS DECIMAL)";
                        $tvDefaultField = "CAST({$tvDefaultField} AS DECIMAL)";
                    }
                } else {
                    $tvValue = $modx->quote($f[1]);
                }
                if ($multiple) {
                    $filterGroup[] =
                        "(EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.name = {$tvName} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id) " .
                        "OR EXISTS (SELECT 1 FROM {$tmplVarTbl} tv WHERE tv.name = {$tvName} AND {$tvDefaultField} {$sqlOperator} {$tvValue} AND tv.id NOT IN (SELECT tmplvarid FROM {$tmplVarResourceTbl} WHERE contentid = modResource.id)) " .
                        ")";
                } else {
                    $filterGroup =
                        "(EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.name = {$tvName} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id) " .
                        "OR EXISTS (SELECT 1 FROM {$tmplVarTbl} tv WHERE tv.name = {$tvName} AND {$tvDefaultField} {$sqlOperator} {$tvValue} AND tv.id NOT IN (SELECT tmplvarid FROM {$tmplVarResourceTbl} WHERE contentid = modResource.id)) " .
                        ")";
                }
            } elseif (count($f) == 1) {
                $tvValue = $modx->quote($f[0]);
                if ($multiple) {
                    $filterGroup[] = "EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id)";
                } else {
                    $filterGroup = "EXISTS (SELECT 1 FROM {$tmplVarResourceTbl} tvr JOIN {$tmplVarTbl} tv ON {$tvValueField} {$sqlOperator} {$tvValue} AND tv.id = tvr.tmplvarid WHERE tvr.contentid = modResource.id)";
                }
            }
        }
        $conditions[] = $filterGroup;
    }
    if (!empty($conditions)) {
        $firstGroup = true;
        foreach ($conditions as $cGroup => $c) {
            if (is_array($c)) {
                $first = true;
                foreach ($c as $cond) {
                    if ($first && !$firstGroup) {
                        $criteria->condition($criteria->query['where'][0][1], $cond, xPDOQuery::SQL_AND, null, $cGroup);
                    } else {
                        $criteria->condition($criteria->query['where'][0][1], $cond, xPDOQuery::SQL_OR, null, $cGroup);
                    }
                    $first = false;
                }
            } else {
                $criteria->condition($criteria->query['where'][0][1], $c, $firstGroup ? xPDOQuery::SQL_OR : xPDOQuery::SQL_AND, null, $cGroup);
            }
            $firstGroup = false;
        }
    }
}

/* include/exclude resources, via &resources=`123,-456` prop */
if (!empty($resources)) {
    $resourceConditions = array();
    $resources = explode(',',$resources);
    $include = array();
    $exclude = array();
    foreach ($resources as $resource) {
        $resource = (int)$resource;
        if ($resource == 0) continue;
        if ($resource < 0) {
            $exclude[] = abs($resource);
        } else {
            $include[] = $resource;
        }
    }
    if (!empty($include)) {
        $criteria->where(array('OR:modResource.id:IN' => $include), xPDOQuery::SQL_OR);
        //$criteria->where(array('AND:modResource.id:IN' => $include), xPDOQuery::SQL_OR);
    }
    if (!empty($exclude)) {
        $criteria->where(array('modResource.id:NOT IN' => $exclude), xPDOQuery::SQL_AND, null, 1);
    }
}

if (!empty($where)) {
    $criteria->where($where);
}

$total = $modx->getCount('modResource', $criteria);
$modx->setPlaceholder($totalVar, $total);

$fields = array_keys($modx->getFields('modResource'));
if (empty($includeContent)) {
    $fields = array_diff($fields, array('content'));
}
$columns = $includeContent ? $modx->getSelectColumns('modResource', 'modResource') : $modx->getSelectColumns('modResource', 'modResource', '', array('content'), true);
$criteria->select($columns);
if (!empty($sortbyTV)) {
    $criteria->leftJoin('modTemplateVar', 'tvDefault', array(
        "tvDefault.name" => $sortbyTV
    ));
    $criteria->leftJoin('modTemplateVarResource', 'tvSort', array(
        "tvSort.contentid = modResource.id",
        "tvSort.tmplvarid = tvDefault.id"
    ));
    if (empty($sortbyTVType)) $sortbyTVType = 'string';
    if ($modx->getOption('dbtype') === 'mysql') {
        switch ($sortbyTVType) {
            case 'integer':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS SIGNED INTEGER) AS sortTV");
                break;
            case 'decimal':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS DECIMAL) AS sortTV");
                break;
            case 'datetime':
                $criteria->select("CAST(IFNULL(tvSort.value, tvDefault.default_text) AS DATETIME) AS sortTV");
                break;
            case 'string':
            default:
                $criteria->select("IFNULL(tvSort.value, tvDefault.default_text) AS sortTV");
                break;
        }
    } elseif ($modx->getOption('dbtype') === 'sqlsrv') {
        switch ($sortbyTVType) {
            case 'integer':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS BIGINT) AS sortTV");
                break;
            case 'decimal':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS DECIMAL) AS sortTV");
                break;
            case 'datetime':
                $criteria->select("CAST(ISNULL(tvSort.value, tvDefault.default_text) AS DATETIME) AS sortTV");
                break;
            case 'string':
            default:
                $criteria->select("ISNULL(tvSort.value, tvDefault.default_text) AS sortTV");
                break;
        }
    }
    $criteria->sortby("sortTV", $sortdirTV);
}
if (!empty($sortby)) {
    if (strpos($sortby, '{') === 0) {
        $sorts = $modx->fromJSON($sortby);
    } else {
        $sorts = array($sortby => $sortdir);
    }
    if (is_array($sorts)) {
        while (list($sort, $dir) = each($sorts)) {
            if ($sortbyEscaped) $sort = $modx->escape($sort);
            if (!empty($sortbyAlias)) $sort = $modx->escape($sortbyAlias) . ".{$sort}";
            $criteria->sortby($sort, $dir);
        }
    }
}
if (!empty($limit)) $criteria->limit($limit, $offset);

if (!empty($debug)) {
    $criteria->prepare();
    $modx->log(modX::LOG_LEVEL_ERROR, $criteria->toSQL());
}
$collection = $modx->getCollection('modResource', $criteria, $dbCacheFlag);

$idx = !empty($idx) || $idx === '0' ? (integer) $idx : 1;
$first = empty($first) && $first !== '0' ? 1 : (integer) $first;
$last = empty($last) ? (count($collection) + $idx - 1) : (integer) $last;

/* include parseTpl */
include_once $modx->getOption('getresources.core_path',null,$modx->getOption('core_path').'components/getresources/').'include.parsetpl.php';

$templateVars = array();
if (!empty($includeTVs) && !empty($includeTVList)) {
    $templateVars = $modx->getCollection('modTemplateVar', array('name:IN' => $includeTVList));
}

if(!empty($featuredResources)) {
    $tmp = array();
    $featuredResources = explode(',', $featuredResources);
    
    foreach($collection as $resourceId => $resource) {
        if(in_array($resource->id, $featuredResources)) {
            $tmp[$resourceId] = $resource;
            unset($collection[$resourceId]);
        }
    }
    
    $collection = array_merge($tmp, $collection);
}

/** @var modResource $resource */
foreach ($collection as $resourceId => $resource) {
    $tvs = array();
    if (!empty($includeTVs)) {
        if (empty($includeTVList)) {
            $templateVars = $resource->getMany('TemplateVars');
        }
        /** @var modTemplateVar $templateVar */
        foreach ($templateVars as $tvId => $templateVar) {
            if (!empty($includeTVList) && !in_array($templateVar->get('name'), $includeTVList)) continue;
            if ($processTVs && (empty($processTVList) || in_array($templateVar->get('name'), $processTVList))) {
                $tvs[$tvPrefix . $templateVar->get('name')] = $templateVar->renderOutput($resource->get('id'));
            } else {
                $value = $templateVar->getValue($resource->get('id'));
                if ($prepareTVs && method_exists($templateVar, 'prepareOutput') && (empty($prepareTVList) || in_array($templateVar->get('name'), $prepareTVList))) {
                    $value = $templateVar->prepareOutput($value);
                }
                $tvs[$tvPrefix . $templateVar->get('name')] = $value;
            }
        }
    }
    $odd = ($idx & 1);
    $properties = array_merge(
        $scriptProperties
        ,array(
            'idx' => $idx
            ,'first' => $first
            ,'last' => $last
            ,'odd' => $odd
        )
        ,$includeContent ? $resource->toArray() : $resource->get($fields)
        ,$tvs
    );
    $resourceTpl = false;
    if ($idx == $first && !empty($tplFirst)) {
        $resourceTpl = parseTpl($tplFirst, $properties);
    }
    if ($idx == $last && empty($resourceTpl) && !empty($tplLast)) {
        $resourceTpl = parseTpl($tplLast, $properties);
    }
    $tplidx = 'tpl_' . $idx;
    if (empty($resourceTpl) && !empty($$tplidx)) {
        $resourceTpl = parseTpl($$tplidx, $properties);
    }
    if ($idx > 1 && empty($resourceTpl)) {
        $divisors = getDivisors($idx);
        if (!empty($divisors)) {
            foreach ($divisors as $divisor) {
                $tplnth = 'tpl_n' . $divisor;
                if (!empty($$tplnth)) {
                    $resourceTpl = parseTpl($$tplnth, $properties);
                    if (!empty($resourceTpl)) {
                        break;
                    }
                }
            }
        }
    }
    if ($odd && empty($resourceTpl) && !empty($tplOdd)) {
        $resourceTpl = parseTpl($tplOdd, $properties);
    }
    if (!empty($tplCondition) && !empty($conditionalTpls) && empty($resourceTpl)) {
        $conTpls = $modx->fromJSON($conditionalTpls);
        $subject = $properties[$tplCondition];
        $tplOperator = !empty($tplOperator) ? $tplOperator : '=';
        $tplOperator = strtolower($tplOperator);
        $tplCon = '';
        foreach ($conTpls as $operand => $conditionalTpl) {
            switch ($tplOperator) {
                case '!=':
                case 'neq':
                case 'not':
                case 'isnot':
                case 'isnt':
                case 'unequal':
                case 'notequal':
                    $tplCon = (($subject != $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '<':
                case 'lt':
                case 'less':
                case 'lessthan':
                    $tplCon = (($subject < $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '>':
                case 'gt':
                case 'greater':
                case 'greaterthan':
                    $tplCon = (($subject > $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '<=':
                case 'lte':
                case 'lessthanequals':
                case 'lessthanorequalto':
                    $tplCon = (($subject <= $operand) ? $conditionalTpl : $tplCon);
                    break;
                case '>=':
                case 'gte':
                case 'greaterthanequals':
                case 'greaterthanequalto':
                    $tplCon = (($subject >= $operand) ? $conditionalTpl : $tplCon);
                    break;
                case 'isempty':
                case 'empty':
                    $tplCon = empty($subject) ? $conditionalTpl : $tplCon;
                    break;
                case '!empty':
                case 'notempty':
                case 'isnotempty':
                    $tplCon = !empty($subject) && $subject != '' ? $conditionalTpl : $tplCon;
                    break;
                case 'isnull':
                case 'null':
                    $tplCon = $subject == null || strtolower($subject) == 'null' ? $conditionalTpl : $tplCon;
                    break;
                case 'inarray':
                case 'in_array':
                case 'ia':
                    $operand = explode(',', $operand);
                    $tplCon = in_array($subject, $operand) ? $conditionalTpl : $tplCon;
                    break;
                case 'between':
                case 'range':
                case '>=<':
                case '><':
                    $operand = explode(',', $operand);
                    $tplCon = ($subject >= min($operand) && $subject <= max($operand)) ? $conditionalTpl : $tplCon;
                    break;
                case '==':
                case '=':
                case 'eq':
                case 'is':
                case 'equal':
                case 'equals':
                case 'equalto':
                default:
                    $tplCon = (($subject == $operand) ? $conditionalTpl : $tplCon);
                    break;
            }
        }
        if (!empty($tplCon)) {
            $resourceTpl = parseTpl($tplCon, $properties);
        }
    }
    if (!empty($tpl) && empty($resourceTpl)) {
        $resourceTpl = parseTpl($tpl, $properties);
    }
    if ($resourceTpl === false && !empty($debug)) {
        $chunk = $modx->newObject('modChunk');
        $chunk->setCacheable(false);
        $output[]= $chunk->process(array(), '<pre>' . print_r($properties, true) .'</pre>');
    } else {
        $output[]= $resourceTpl;
    }
    $idx++;
}

/* output */
$toSeparatePlaceholders = $modx->getOption('toSeparatePlaceholders', $scriptProperties, false);
if (!empty($toSeparatePlaceholders)) {
    $modx->setPlaceholders($output, $toSeparatePlaceholders);
    return '';
}

$output = implode($outputSeparator, $output);

$tplWrapper = $modx->getOption('tplWrapper', $scriptProperties, false);
$wrapIfEmpty = $modx->getOption('wrapIfEmpty', $scriptProperties, false);
if (!empty($tplWrapper) && ($wrapIfEmpty || !empty($output))) {
    $output = parseTpl($tplWrapper, array_merge($scriptProperties, array('output' => $output)));
}

$toPlaceholder = $modx->getOption('toPlaceholder', $scriptProperties, false);
if (!empty($toPlaceholder)) {
    $modx->setPlaceholder($toPlaceholder, $output);
    return '';
}
return $output;

The way the MODx parser works, with a structure like this

[[!getQuery:is=`featured`:then=`
	[[!mySnippet?myProperty=`is featured`]]
`:else=`
	[[!mySnippet?myProperty=`not featured`]]
`]]
//Snippet "mySnippet"
<?php
$modx->log(modX::LOG_LEVEL_ERROR,'mySnippet called with ' . $myProperty);
return $myProperty;

the snippet mySnippet is executed twice on every request no matter the return value of getQuery.

This means that in your case the snippet getResourcesModified2 is executed uncached three times on every request (which is not optimal).

Maybe read this article for more information about the parser and tag order.

This topic was automatically closed 2 days after discussion ended and a solution was marked. New replies are no longer allowed. You can open a new topic by clicking the link icon below the original post or solution and selecting “+ New Topic”.