Answers.Body: <p>After too much time spent on this, I think I found a solution to both issues.</p>
  
  <p>1) To prevent controller actions meant for the CP from being accessible on the front-end (via <code>actions/&lt;module&gt;/&lt;controller&gt;/&lt;action&gt;</code> URLs), simply add this line of code at the top of the action's method:</p>
  
  <pre><code>$this-&gt;requireCpRequest();
  </code></pre>
  
  <p>The non-CP action URLs will now return a <code>400</code> error.</p>
  
  <p>2) Although it doesn't seem possible to change the private template trigger on the CP to prevent any template from being directly accessed with a URL, using an underscore at the beginning of any file or folder in the path works. But even better -- the template root itself can be private:</p>
  
  <pre><code>// Set the base CP template directory
  Event::on(
      View::class,
      View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
      function(craft\events\RegisterTemplateRootsEvent $event) {
          $event-&gt;roots['_module'] = $this-&gt;getBasePath() . DIRECTORY_SEPARATOR . 'templates';
      }
  );
  </code></pre>
  
  <p>Notice the underscore in front of <code>_module</code>. This ensures that the module's templates will only render programmatically.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-02-07T17:45:48.023Z
Answers.LastActivityDate: 2019-02-07T17:45:48.023Z
Answers.UserDisplayName: benface
Answers.Body: <p>I think you're "fighting the system" for no great gain, but anyway I think this is what you want:</p>
  
  <pre><code>    /**
       * @var bool|string[] Whether this controller’s actions can be accessed anonymously
       * If set to false, you are required to be logged in to execute any of the given controller's actions.
       * If set to true, anonymous access is allowed for all of the given controller's actions.
       * If the value is an array of action IDs, then you must be logged in for any actions except for the ones in
       * the array list.
       * If you have a controller that where the majority of actions allow anonymous access, but you only want require
       * login on a few, you can set this to true and call [[requireLogin()]] in the individual methods.
       */
      protected $allowAnonymous = false;
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-02-07T18:57:13.150Z
Answers.LastActivityDate: 2019-02-07T18:57:13.150Z
Answers.UserDisplayName: andrew.welch
Body: <p>I have a custom module that is adding sections to the CP, like this:</p>
  
  <pre><code>// Register CP routes
  Event::on(
      UrlManager::class,
      UrlManager::EVENT_REGISTER_CP_URL_RULES,
      function(craft\events\RegisterUrlRulesEvent $event) {
          $event-&gt;rules['&lt;controller:(subscriptions|purchases)&gt;/&lt;action:&gt;'] = [
              'route' =&gt; 'module/&lt;controller&gt;/&lt;action&gt;', 
              'defaults' =&gt; [
                  'action' =&gt; 'index',
              ],
          ];
      }
  );
  
  // Add CP nav items
  Event::on(
      Cp::class,
      Cp::EVENT_REGISTER_CP_NAV_ITEMS,
      function(craft\events\RegisterCpNavItemsEvent $event) {
          $event-&gt;navItems[] = [
              'url' =&gt; 'subscriptions',
              'label' =&gt; 'Subscriptions',
          ];
          $event-&gt;navItems[] = [
              'url' =&gt; 'purchases',
              'label' =&gt; 'Purchases',
          ];
      }
  );
  </code></pre>
  
  <p>I have a <code>SubscriptionsController</code> and a <code>PurchasesController</code>, inside of which there is an <code>actionIndex()</code> method that computes some stuff and renders the appropriate template like this:</p>
  
  <pre><code>return $this-&gt;renderTemplate('module/cp/subscriptions/index', [
      'stuff' =&gt; $stuff,
  ]);
  </code></pre>
  
  <p>All this works fine, but I realized that the above registers some URLs that I do not want:</p>
  
  <p>1) Controllers automatically register URLs in the format <code>admin/actions/&lt;module&gt;/&lt;controller&gt;/&lt;action&gt;</code> AND <code>actions/&lt;module&gt;/&lt;controller/&lt;action&gt;</code>. I'm fine with the first one even though it causes duplicate URLs due to my custom <code>&lt;controller:(subscriptions|purchases)&gt;/&lt;action:&gt;</code> rule, but the second one is problematic because they're not even CP URLs, yet they invoke a controller that is only meant to be used in the CP. It's causing an error when trying to render the template, because the <code>module</code> template root is only registered for the CP with the code below:</p>
  
  <pre><code>// Set the base CP template directory
  Event::on(
      View::class,
      View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
      function (craft\events\RegisterTemplateRootsEvent $event) {
          $event-&gt;roots['module'] = $this-&gt;getBasePath() . DIRECTORY_SEPARATOR . 'templates';
      }
  );
  </code></pre>
  
  <p>2) Craft automatically creates routes based on my template structure, meaning I can render the <code>module/subscriptions/index.twig</code> template directly from this URL: <code>admin/module/subscriptions</code>. This is also causing an error because the template requires variables that are passed to it in the controller action, but they're not set when rendering the template directly. I usually work around this issue on the front-end by setting the <code>general.php</code> config setting <code>privateTemplateTrigger</code> to an empty string <code>''</code>, but it doesn't seem to have any effect on the CP.</p>
  
  <p>Any help with these two issues would be greatly appreciated. :)</p>
  
Tags: <plugin-development><craft3><modules><yii>
Title: How do I prevent a controller from creating unwanted routes?
ThreadText: # How do I prevent a controller from creating unwanted routes?
  
  (Asked by: username_0 on 2019-02-06)
  
  I have a custom module that is adding sections to the CP, like this:
  
  ```
  // Register CP routes
  Event::on(
      UrlManager::class,
      UrlManager::EVENT_REGISTER_CP_URL_RULES,
      function(craft\events\RegisterUrlRulesEvent $event) {
          $event->rules['<controller:(subscriptions|purchases)>/<action:>'] = [
              'route' => 'module/<controller>/<action>', 
              'defaults' => [
                  'action' => 'index',
              ],
          ];
      }
  );
  
  // Add CP nav items
  Event::on(
      Cp::class,
      Cp::EVENT_REGISTER_CP_NAV_ITEMS,
      function(craft\events\RegisterCpNavItemsEvent $event) {
          $event->navItems[] = [
              'url' => 'subscriptions',
              'label' => 'Subscriptions',
          ];
          $event->navItems[] = [
              'url' => 'purchases',
              'label' => 'Purchases',
          ];
      }
  );
  
  ```
  
  I have a `SubscriptionsController` and a `PurchasesController`, inside of which there is an `actionIndex()` method that computes some stuff and renders the appropriate template like this:
  
  ```
  return $this->renderTemplate('module/cp/subscriptions/index', [
      'stuff' => $stuff,
  ]);
  
  ```
  
  All this works fine, but I realized that the above registers some URLs that I do not want:
  
  1) Controllers automatically register URLs in the format `admin/actions/<module>/<controller>/<action>` AND `actions/<module>/<controller/<action>`. I'm fine with the first one even though it causes duplicate URLs due to my custom `<controller:(subscriptions|purchases)>/<action:>` rule, but the second one is problematic because they're not even CP URLs, yet they invoke a controller that is only meant to be used in the CP. It's causing an error when trying to render the template, because the `module` template root is only registered for the CP with the code below:
  
  ```
  // Set the base CP template directory
  Event::on(
      View::class,
      View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
      function (craft\events\RegisterTemplateRootsEvent $event) {
          $event->roots['module'] = $this->getBasePath() . DIRECTORY_SEPARATOR . 'templates';
      }
  );
  
  ```
  
  2) Craft automatically creates routes based on my template structure, meaning I can render the `module/subscriptions/index.twig` template directly from this URL: `admin/module/subscriptions`. This is also causing an error because the template requires variables that are passed to it in the controller action, but they're not set when rendering the template directly. I usually work around this issue on the front-end by setting the `general.php` config setting `privateTemplateTrigger` to an empty string `''`, but it doesn't seem to have any effect on the CP.
  
  Any help with these two issues would be greatly appreciated. :)
  
  **Tags:** plugin-development, craft3, modules, yii
  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_0 on 2019-02-07. Score: 1 | Accepted answer
  
  After too much time spent on this, I think I found a solution to both issues.
  
  1) To prevent controller actions meant for the CP from being accessible on the front-end (via `actions/<module>/<controller>/<action>` URLs), simply add this line of code at the top of the action's method:
  
  ```
  $this->requireCpRequest();
  
  ```
  
  The non-CP action URLs will now return a `400` error.
  
  2) Although it doesn't seem possible to change the private template trigger on the CP to prevent any template from being directly accessed with a URL, using an underscore at the beginning of any file or folder in the path works. But even better -- the template root itself can be private:
  
  ```
  // Set the base CP template directory
  Event::on(
      View::class,
      View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
      function(craft\events\RegisterTemplateRootsEvent $event) {
          $event->roots['_module'] = $this->getBasePath() . DIRECTORY_SEPARATOR . 'templates';
      }
  );
  
  ```
  
  Notice the underscore in front of `_module`. This ensures that the module's templates will only render programmatically.
  
  
  ---
  
  ### Answer by username_1 on 2019-02-07. Score: 1
  
  I think you're "fighting the system" for no great gain, but anyway I think this is what you want:
  
  ```
      /**
       * @var bool|string[] Whether this controller’s actions can be accessed anonymously
       * If set to false, you are required to be logged in to execute any of the given controller's actions.
       * If set to true, anonymous access is allowed for all of the given controller's actions.
       * If the value is an array of action IDs, then you must be logged in for any actions except for the ones in
       * the array list.
       * If you have a controller that where the majority of actions allow anonymous access, but you only want require
       * login on a few, you can set this to true and call [[requireLogin()]] in the individual methods.
       */
      protected $allowAnonymous = false;
  
  ```
  
  
  ---
  
  