Skip to content

Add 'My Collection Only' filter to card search - #200

Open
robertclayh wants to merge 2 commits into
seastan:masterfrom
robertclayh:feature/my-collection-filter
Open

Add 'My Collection Only' filter to card search#200
robertclayh wants to merge 2 commits into
seastan:masterfrom
robertclayh:feature/my-collection-filter

Conversation

@robertclayh

@robertclayh robertclayh commented Mar 20, 2026

Copy link
Copy Markdown

Summary

Adds a "My Collection Only" toggle button to the card search results page that filters results to show only cards from packs the user has saved in their collection.

  • Toggle is only visible to logged-in users
  • Filter persists across pagination
  • Uses the existing owned_packs data from the user's saved collection settings

Addresses #114 (specifically the "My Collection" search category suggestion)

Changes

  • SearchController.php — reads collection query parameter, fetches user's owned packs, passes them through search and pagination
  • searchbar.html.twig — adds the toggle checkbox button to the search bar
  • CardsData.php — adds optional $ownedPacks parameter to get_search_rows() to filter by pack IDs

Test plan

  • Log in and go to /search, search for any cards (e.g. t:hero)
  • Verify "My Collection Only" toggle appears next to sort/view options
  • Check the toggle and search again — results should only include cards from owned packs
  • Verify pagination links preserve the collection filter
  • Verify the toggle does not appear when logged out

Adds a toggle button on the search results page that filters cards
to only show those from packs the user owns in their saved collection.
Only visible to logged-in users.

Closes seastan#114

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
$page = $request->query->get('page') ?: 1;
$view = $request->query->get('view') ?: 'list';
$sort = $request->query->get('sort') ?: 'name';
$collection = $request->query->get('collection') ?: 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$collection = $request->query->get('collection') ?: 0;
$onlyCollections = $request->query->get('collection') ?: 0;

bool variable should usually be named distinctively

}
$qb->andWhere(implode(" or ", $packOr));
}

@shivuvano shivuvano Jul 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposed version is inefficient and can be improved by the folowing:

  1. No duplicate cards in results
  2. Stops searching after first match per card (faster)
  3. No extra DISTINCT needed
  4. Matches existing codebase pattern
  5. DB optimizer knows exactly what you want
if ($ownedPacks !== null && count($ownedPacks) > 0) {
    $packIds = array_map(fn($p) => $p->getId(), $ownedPacks->toArray());
    $qb->andWhere(
        "EXISTS (SELECT cpo$i.id FROM AppBundle:CardPrinting cpo$i " .
        "JOIN cpo$i.pack ppo$i " .
        "WHERE cpo$i.card = c AND ppo$i.id IN (:ownedPackIds))"
    )->setParameter('ownedPackIds', $packIds);
    $i++;
}

Co-authored-by: shivuvano <46002432+shivuvano@users.noreply.github.com>

@robertclayh robertclayh left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@seastan

seastan commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Bugs (feature does not work as shipped)

  1. Undefined variable breaks the filter at the entry point. In findAction (SearchController.php):
    $onlyCollections = $request->query->get('collection') ?: 0;
    ...
    return $this->forward('AppBundle:Search:display', [
    ...
    'collection' => $collection, // <-- should be $onlyCollections
    ...
    ]);
  2. $collection is never defined in this method's scope. This is the code path the search form actually submits to (cards_find route → findAction), so the forwarded collection value is always null/falsy regardless of what the user checked. displayAction's own $collection param — and therefore the entire $ownedPacks lookup — never receives a truthy value through normal use.
  3. The new DQL filter references a query alias that doesn't exist. In CardsData.php:
    $packOr[] = "(p.id = ?$i)";
  4. get_search_rows()'s query builder only ever aliases c (Card), t (type), and s (sphere) — there is no p/Pack alias anywhere in this method. Every other pack-related condition in this file (e.g. the case 'e' pack filter, case 'x' cycle filter) reaches Pack through a CardPrinting subquery (JOIN cpe{$i}.pack ppe{$i} ... WHERE cpe{$i}.card = c), since Card has no direct relation to Pack. As written, p.id = ? will throw a Doctrine QueryException ("p" is not defined) the moment this branch executes with a non-empty $ownedPacks — i.e. as soon as bug Rules Section #1 is also fixed, this crashes instead of filtering. It needs to follow the existing subquery pattern via CardPrinting, e.g. EXISTS (SELECT cpo{$i}.id FROM AppBundle:CardPrinting cpo{$i} JOIN cpo{$i}.pack ppo{$i} WHERE cpo{$i}.card = c AND ppo{$i}.id IN (...)).
  5. owned_packs isn't a plain CSV of pack IDs — the parsing is wrong. Per the comment in CollectionController.php:17 (and repeated in FellowshipController.php, QuestLogController.php, SocialController.php): "owned_packs is a per-pack COUNT map encoded as id / id:count tokens (legacy id-2/id-3)". The PR does:
    $ownedPacks = explode(',', $ownedPacksStr);
  6. with no stripping of the :count/-count suffix. So a token like "42:3" gets compared literally as p.id = "42:3", which will never match a real pack ID. Any user whose collection has a multi-copy pack recorded (which is common) will have that pack silently excluded from the filter. The existing parsing pattern in CollectionController.php:19-28 (regex over ^(\d+):(\d+)$ and legacy ^(\d+)(?:-\d+)?$) should be reused/extracted rather than re-implemented via naive explode.

Merge status

gh pr view 200 reports mergeable: CONFLICTING / mergeStateStatus: DIRTY. Master has since added a $selected_pack_code = null 7th parameter to displayAction() (from a different, already-merged PR), which collides with this PR's $collection = 0 7th parameter on the same signature line. This branch needs a rebase — worth flagging to the author regardless of the bugs above, since the diff you review on GitHub won't reflect this collision.

Minor / style

  • is_authenticated is passed from displayAction into the template purely to gate the checkbox, but Twig already exposes the logged-in user globally as app.user in every template in this codebase (used elsewhere, e.g. Search/display-card.html.twig). Simpler to check {% if app.user %} in searchbar.html.twig and drop the new controller variable.
  • The SQL parameter binding itself is done correctly (positional setParameter, no string interpolation of user input into the query) — no injection risk in the CardsData.php change, just the alias/parsing bugs above.
  • Not a blocker given no existing PHP test suite exercises this controller, but worth a manual re-test end-to-end (checkbox → submit → verify actual filtering, not just checkbox visual state) once the three bugs are fixed, since none of them would surface from a quick visual glance at the diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants