2

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. :)

2 Answers 2

1

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.

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;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.