Go back
Thumbnail doefom/restrict - Statamic Addon

My "Restrict" Statamic Addon - From Zero to Personal Hero

My GitHub project description says:

A Statamic addon that applies your EntryPolicy's view method to entry listings in the control panel.

And that really sums it up quite well. The idea for this addon originated from an actual use case I had at work. In this Statamic application we had a few entities to deal with:

  • Companies: Each company is unique, has many job offers and multiple users can be assigned to this company.

  • Jobs: One job belongs to one company and has at least one location but can also have multiple locations.

  • Locations: A location belongs to a company and jobs can have multiple locations assigned.

  • User: As always there are users and in this case each user belongs to one company.

So the goal was to make sure each user, when logged in, can only see those jobs and locations that are assigned to the same company as the user.

Easy peasy I thought, just extend Statamic's EntryPolicy, adjust the view method and you're done. Obviously this didn't work. This is due to the fact that in the control panel listings of entries do not respect the EntryPolicy's view method. When I finally figured out how the EntryRepository works and how entries are fetched from the store, I thought I might not be the only one facing this problem. So I wrapped my shitty code in an addon and made it adjustable. And well, here it is.

How does it work?

Basically my addon does one thing: Adjusting the EntryQueryBuilder so that the EntryPolicy's view method is applied to the entries when they're fetched from the store.

First of all, we need to rebind Statamic's original EntryQueryBuilder to our custom one in the addon's ServiceProvider:

1<?php
2 
3namespace Doefom\Restrict;
4 
5class ServiceProvider extends AddonServiceProvider
6{
7 public function bootAddon(): void
8 {
9 // Rebinding the EntryQueryBuilder
10 $this->app->bind(\Statamic\Stache\Query\EntryQueryBuilder::class, function ($app) {
11 return new EntryQueryBuilder($app['stache']->store('entries'));
12 });
13 }
14}

To apply the policy's view method I override the getFilteredKeys method of the default EntryQueryBuilder. This is the part responsible for cases when you write something like Entry::query()->where('collection', 'jobs')->get(). And my adjustments look like this:

1<?php
2 
3class EntryQueryBuilder extends StatamicEntryQueryBuilder
4{
5 protected function getFilteredKeys()
6 {
7 $resKeys = parent::getFilteredKeys();
8 
9 $user = User::current();
10 
11 if (! Restrict::shouldApplyRestriction($user)) {
12 return $resKeys;
13 }
14 
15 $entryPolicy = app(EntryPolicy::class);
16 
17 if (get_class($entryPolicy) === EntryPolicy::class) {
18 // No custom policy is defined, so we don't need to filter the keys.
19 return $resKeys;
20 }
21 
22 return $resKeys->filter(function ($key) use ($user, $entryPolicy) {
23 $entry = $this->store->getItem($key);
24 
25 return $entryPolicy->view($user, $entry);
26 });
27 }
28}

So as mentioned above, this method is responsible for fetching the entries from the store. Let's breakdown what my overridden method does:

  1. Get the keys for the entries as you usually would. The keys define which entries to later fetch from the store, so that's before you have an actual Entry object at hand. Until then it's just the key, a string.

  2. Check if the EntryPolicy should be applied in the current request, given the current route and the user that is trying to access the page.

  3. If so, spin up the EntryPolicy and check if it is a custom one. Otherwise just return the keys as before since no changes have been made.

  4. Go through each key, fetch the corresponding entry from the store, run the view method on it and return only those keys where view returns true.

That's pretty much it. First of all, this tiny bit of code took me many many hours to write. And second, my code is (quite certainly) inefficient. Because what happens is:

  • It gets the keys which it would anyway

  • Fetches each entry from the store and checks the condition

  • Then returns the keys only, not the entries

  • To later fetch those entries by the same keys again

That's why I'm quite certain it's inefficient. But it works for now.

Why it's My Personal Hero?

Because there are so many applications I can use it for. Some already built, some I think I know might be built. For the beginning it was just fun to implement the addon and release it so other people can use it and see how they like it.

A little later it became my most popular addon so far! You know it's a little more than 400+ installs (as of 04.06.2024) so it's not a big deal. But imagining 400 people standing in front of me where everyone has downloaded my addon is kind of cool.

Thanks a lot for reading this far and if you want to check out the current state of the project on GitHub, there you go: https://github.com/doefom/restrict

Have a great day and best regards
Dominik