<p>I had a similar problem where I needed to manage national and regional users groups where regional users should only see their own content. I didn’t like the idea of overriding the whole <code>ElementIndexesController</code> controller as most of its properties and methods are private and that could impede upgrades to Craft.</p>

<p>I managed to have this work by using <code>ElementQuery::EVENT_BEFORE_PREPARE</code> in my site module.</p>

<p>Regional content is assigned a region category with an attribute (numeroRegion) that match the last 2 characters of the regional group handle (ex.: "01").</p>

<p>Note that I’m using the Collections plugin to filter and map the groups.</p>

<pre><code>if ($user &amp;&amp; $user-&gt;id &amp;&amp; !$user-&gt;identity-&gt;admin &amp;&amp; $request-&gt;getIsCpRequest()) {
  Event::on(ElementQuery::class, ElementQuery::EVENT_BEFORE_PREPARE, function(Event $event) use ($user) {

    if ($event-&gt;sender-&gt;elementType === 'craft\elements\Entry') {
        $userGroups = Collections::$plugin-&gt;collectionsService-&gt;collect(Craft::$app-&gt;userGroups-&gt;getGroupsByUserId($user-&gt;id));
      $isNational = $userGroups-&gt;filter(function($group) {
        return (int)$group-&gt;id === 1;
      })-&gt;isNotEmpty();

      if (!$isNational) {
        $userRegionGroups = $userGroups-&gt;map(function($group) {
          return substr($group-&gt;handle, -2);
        });
        $regions = Category::find()-&gt;numeroRegion($userRegionGroups)-&gt;all();
        Craft::configure($event-&gt;sender, [
          'relatedTo' =&gt; [
            'element' =&gt; $regions
          ]
        ]);
      }
    }
  });
}
</code></pre>

<p>Disclaimer: it works, but this is my first attempt at this kind of hack, and it may have side effects I haven’t noticed yet.</p>

CC BY-SA 4.0
2018-06-19T12:29:46.643Z
2018-06-19T12:29:46.643Z
lemp
<p>There is a way - it's actually quite easy but you need to dig deep into Craft 3 to do it. In general: there is only one single Javascript function that fetches all elements for all index pages</p>

<pre><code>updateElements: function() {
    // Ignore if we're not fully initialized yet
    if (!this.initialized) {
        return;
    }

    this.setIndexBusy();

    var params = this.getViewParams();

    Craft.postActionRequest(this.settings.updateElementsAction, params, $.proxy(function(response, textStatus) {
        this.setIndexAvailable();

        if (textStatus === 'success') {
            this._updateView(params, response);
        }
        else {
            Craft.cp.displayError(Craft.t('app', 'An unknown error occurred.'));
        }

    }, this));
},
</code></pre>

<p>As you can see: the route is a variable in the <code>defaults</code></p>

<pre><code>updateElementsAction: 'element-indexes/get-elements'
</code></pre>

<p>In order to change this route you'll have to overwrite it in a JS file</p>

<pre><code>Craft.BaseElementIndex.defaults.updateElementsAction = 'your-awesome-plugin-route';
</code></pre>

<p>Not let's take a look at the controller: After certain other functions the final call that searches for all elements is the following <a href="https://github.com/craftcms/cms/blob/develop/src/controllers/ElementIndexesController.php#L379" rel="nofollow noreferrer">ElementIndexController line 379</a></p>

<pre><code>$responseData['html'] = $elementType::indexHtml(
    $this-&gt;_elementQuery,  // &lt;-- this is the important part
    $disabledElementIds,
    $this-&gt;_viewState,
    $this-&gt;_sourceKey,
    $this-&gt;_context,
    $includeContainer,
    $showCheckboxes
);
</code></pre>

<p>All you have to do is to extend this Controller, build a custom one, link the route to your custom controller and change the <code>$this-&gt;_elementQuery</code>. You can include your custom search parameters, include hidden content fields, create custom relation fields for groups and search for elements related to this user group or whatever you want.</p>

<p>It seems a little bit tricky at first but it's actually pretty easy. I've done it several times</p>

CC BY-SA 3.0
2018-04-16T13:36:49.097Z
Thanks Robin! Where is the JS file you mention above? And how can I create and inject a new JS file to overwrite it to use the new route?
Ian Young
CC BY-SA 3.0
2018-04-16T14:45:58.987Z
@Ian You need to register a new Assetbundle https://github.com/craftcms/docs/blob/v3/en/asset-bundles.md somewhere in your plugin
Robin Schambach
CC BY-SA 3.0
2018-04-16T16:29:48.983Z
Thanks again @Robin. That gives me something to go on. And I see what you were talking about in regard to the JS file in Craft.js on line 3466. Do you have any examples of a working plugin btw? Whilst learning it can often be useful to pick apart something very similar
Ian Young
CC BY-SA 3.0
2018-04-16T18:17:36.213Z
@Ian I can only provide code snippeds because I never build a "single" plugin with that ability. I have a "default" plugin that is used in every single project that contains the functionality of many plugins at the store... Including  permissions based on entries, certain fields and on sites. But it replaces many Craft files with closures and the Yii2 classmap and its specialized to my customers - it digs really deep in Craft in order to handle things quite complex. It's not good for leaning purpose. But you can  ping me on slack if you have further questions
Robin Schambach
CC BY-SA 3.0
2018-05-01T00:11:03.660Z
@Robin, just checking that I understand correctly. Add a plugin with a controller. All admin pages get the data that they display through ajax using javascript. Change the js file to use the route of my custom controller. Add logic to my controller to achieve displaying what I want. Is this correct?
Eric Snyder
CC BY-SA 3.0
2018-05-01T08:34:26.137Z
@EricSnyder not all pages in the controll panel receive their data with ajax - but the elements displayed via `BaseElementIndex`. You should never change files, that's a no go because your changes are overwritten in the next update. Instead you should implement a Javascript function that changes the settings in order to route the request to a custom controller. Maybe you mean the correct thing but what you say is not really correct
Robin Schambach
CC BY-SA 4.0
2018-05-05T16:42:00.967Z
@RobinSchamback - you are correct. I misspoke when I said "change the file". I find two code fragments in Craft like the one above. One is in the file craft\src\web\assets\cp\src\js\BaseElementIndex.js and one in the file craft\src\web\assets\cp\dist\js\Craft.js. I want to use BaseElementIndex.js correct? What is the difference between the two files? What do both files do?
Eric Snyder
CC BY-SA 4.0
2018-05-05T17:02:47.373Z
Craft.js does not contain such a function https://github.com/craftcms/cms/blob/develop/src/web/assets/cp/src/js/Craft.js. Not sure what you mean by "I want to use BaseElementIndex" you don't want to "use" any  of these files
Robin Schambach
CC BY-SA 4.0
2018-05-05T18:01:18.430Z
@RobinSchamback - Uhhh...https://github.com/craftcms/cms/blob/develop/src/web/assets/cp/src/js/BaseElementIndex.js. I mean use that file as a template to in your words "In order to change this route you'll have to overwrite it in a JS file"
Eric Snyder
CC BY-SA 4.0
2018-05-05T18:11:26.763Z
Sorry I have no idea what you mean or what you want to know
Robin Schambach
CC BY-SA 4.0
2018-05-05T18:14:28.160Z
I am clearly misunderstanding your instructions above. I'll study further and see if I can sort it out. If I have a new question I'll ask in a new post and point to this post.
Eric Snyder
CC BY-SA 3.0
2018-04-16T12:48:19.473Z
2018-04-16T12:48:19.473Z
Robin Schambach
<p>Working in Craft 3, I have a requirement to restrict user access (in the CP) <strong><em>per Entry</em></strong> rather than <strong><em>per Section</em></strong>. </p>

<p>In this case Users in Craft will belong to Department (Channel) and Resources (Channel). They should only be able to administer the Entries in those Channels for which they are responsible.</p>

<p>I suppose the ideal would be to create a User Group per Department and then be able to <strong>select the individual Entries within the Department &amp; Resources Channels</strong> here:</p>

<p><a href="https://i.sstatic.net/SWzsv.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/SWzsv.png" alt="enter image description here"></a></p>

<p>Is that possible via a Module/Plugin and if so can anyone provide any pointers on how to get started? I presume I'd need to loop through all Entries in the Channels and output them in this list to be selectable and then have access permissions applied. Any examples or pointers would be appreciated.</p>

<p>Any other suggestions or workarounds also welcome.</p>

<p>Thanks</p>

<permissions>
Restrict Permissions Per Entry Rather Than Per Section
# Restrict Permissions Per Entry Rather Than Per Section

(Asked by: username_0 on 2018-04-16)

Working in Craft 3, I have a requirement to restrict user access (in the CP) ***per Entry*** rather than ***per Section***.

In this case Users in Craft will belong to Department (Channel) and Resources (Channel). They should only be able to administer the Entries in those Channels for which they are responsible.

I suppose the ideal would be to create a User Group per Department and then be able to **select the individual Entries within the Department & Resources Channels** here:

[![enter image description here](https://i.sstatic.net/SWzsv.png)](https://i.sstatic.net/SWzsv.png)

Is that possible via a Module/Plugin and if so can anyone provide any pointers on how to get started? I presume I'd need to loop through all Entries in the Channels and output them in this list to be selectable and then have access permissions applied. Any examples or pointers would be appreciated.

Any other suggestions or workarounds also welcome.

Thanks

**Tags:** permissions


---

## 2 answers

### Answer by username_2 on 2018-04-16. Score: 4 | Accepted answer

There is a way - it's actually quite easy but you need to dig deep into Craft 3 to do it. In general: there is only one single Javascript function that fetches all elements for all index pages
updateElements: function() {
    // Ignore if we're not fully initialized yet
    if (!this.initialized) {
        return;
    }

    this.setIndexBusy();

    var params = this.getViewParams();

    Craft.postActionRequest(this.settings.updateElementsAction, params, $.proxy(function(response, textStatus) {
        this.setIndexAvailable();

        if (textStatus === 'success') {
            this._updateView(params, response);
        }
        else {
            Craft.cp.displayError(Craft.t('app', 'An unknown error occurred.'));
        }

    }, this));
},

As you can see: the route is a variable in the defaults
updateElementsAction: 'element-indexes/get-elements'

In order to change this route you'll have to overwrite it in a JS file
Craft.BaseElementIndex.defaults.updateElementsAction = 'your-awesome-plugin-route';

Not let's take a look at the controller: After certain other functions the final call that searches for all elements is the following ElementIndexController line 379
$responseData['html'] = $elementType::indexHtml(
    $this->_elementQuery,  // <-- this is the important part
    $disabledElementIds,
    $this->_viewState,
    $this->_sourceKey,
    $this->_context,
    $includeContainer,
    $showCheckboxes
);

All you have to do is to extend this Controller, build a custom one, link the route to your custom controller and change the $this->_elementQuery. You can include your custom search parameters, include hidden content fields, create custom relation fields for groups and search for elements related to this user group or whatever you want.
It seems a little bit tricky at first but it's actually pretty easy. I've done it several times

> **Comments:**
> **username_0**: Thanks Robin! Where is the JS file you mention above? And how can I create and inject a new JS file to overwrite it to use the new route?  
> **username_2**: @Ian You need to register a new Assetbundle https://github.com/craftcms/docs/blob/v3/en/asset-bundles.md somewhere in your plugin  
> **username_0**: Thanks again @Robin. That gives me something to go on. And I see what you were talking about in regard to the JS file in Craft.js on line 3466. Do you have any examples of a working plugin btw? Whilst learning it can often be useful to pick apart something very similar  
> **username_2**: @Ian I can only provide code snippeds because I never build a "single" plugin with that ability. I have a "default" plugin that is used in every single project that contains the functionality of many plugins at the store... Including  permissions based on entries, certain fields and on sites. But it replaces many Craft files with closures and the Yii2 classmap and its specialized to my customers - it digs really deep in Craft in order to handle things quite complex. It's not good for leaning purpose. But you can  ping me on slack if you have further questions  
> **username_3**: @Robin, just checking that I understand correctly. Add a plugin with a controller. All admin pages get the data that they display through ajax using javascript. Change the js file to use the route of my custom controller. Add logic to my controller to achieve displaying what I want. Is this correct?  
> **username_2**: @EricSnyder not all pages in the controll panel receive their data with ajax - but the elements displayed via `BaseElementIndex`. You should never change files, that's a no go because your changes are overwritten in the next update. Instead you should implement a Javascript function that changes the settings in order to route the request to a custom controller. Maybe you mean the correct thing but what you say is not really correct  
> **username_3**: @RobinSchamback - you are correct. I misspoke when I said "change the file". I find two code fragments in Craft like the one above. One is in the file craft\src\web\assets\cp\src\js\BaseElementIndex.js and one in the file craft\src\web\assets\cp\dist\js\Craft.js. I want to use BaseElementIndex.js correct? What is the difference between the two files? What do both files do?  
> **username_2**: Craft.js does not contain such a function https://github.com/craftcms/cms/blob/develop/src/web/assets/cp/src/js/Craft.js. Not sure what you mean by "I want to use BaseElementIndex" you don't want to "use" any  of these files  
> **username_3**: @RobinSchamback - Uhhh...https://github.com/craftcms/cms/blob/develop/src/web/assets/cp/src/js/BaseElementIndex.js. I mean use that file as a template to in your words "In order to change this route you'll have to overwrite it in a JS file"  
> **username_2**: Sorry I have no idea what you mean or what you want to know  
> **username_3**: I am clearly misunderstanding your instructions above. I'll study further and see if I can sort it out. If I have a new question I'll ask in a new post and point to this post.  

---

### Answer by username_1 on 2018-06-19. Score: 3

I had a similar problem where I needed to manage national and regional users groups where regional users should only see their own content. I didn’t like the idea of overriding the whole ElementIndexesController controller as most of its properties and methods are private and that could impede upgrades to Craft.
I managed to have this work by using ElementQuery::EVENT_BEFORE_PREPARE in my site module.
Regional content is assigned a region category with an attribute (numeroRegion) that match the last 2 characters of the regional group handle (ex.: "01").
Note that I’m using the Collections plugin to filter and map the groups.
if ($user && $user->id && !$user->identity->admin && $request->getIsCpRequest()) {
  Event::on(ElementQuery::class, ElementQuery::EVENT_BEFORE_PREPARE, function(Event $event) use ($user) {

    if ($event->sender->elementType === 'craft\elements\Entry') {
        $userGroups = Collections::$plugin->collectionsService->collect(Craft::$app->userGroups->getGroupsByUserId($user->id));
      $isNational = $userGroups->filter(function($group) {
        return (int)$group->id === 1;
      })->isNotEmpty();

      if (!$isNational) {
        $userRegionGroups = $userGroups->map(function($group) {
          return substr($group->handle, -2);
        });
        $regions = Category::find()->numeroRegion($userRegionGroups)->all();
        Craft::configure($event->sender, [
          'relatedTo' => [
            'element' => $regions
          ]
        ]);
      }
    }
  });
}

Disclaimer: it works, but this is my first attempt at this kind of hack, and it may have side effects I haven’t noticed yet.


---

