Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW AdminController as superclass for /admin/* routed controllers #1836

Open
wants to merge 2 commits into
base: 3
Choose a base branch
from

Conversation

GuySartorelli
Copy link
Member

There are two commits here:

  1. Mostly-unrelated refactor to use config instead of runtime code to remove the CMSProfileController menu item
  2. Add the new AdminController class and wire it up

I've also added a bunch of strong typing to the new class and to AdminRouteController.
I have not added strong typing to LeftAndMain though that may happen as part of a follow-up card in this epic.
I have not added strong typing to methods which would required extensive trickle-down effects (e.g. init() and canView()).

Issue

@@ -31,5 +31,3 @@
]);
// enable ability to insert anchors
$editorConfig->insertButtonsAfter('sslink', 'anchor');

CMSMenu::remove_menu_class(CMSProfileController::class);
Copy link
Member Author

Choose a reason for hiding this comment

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

That's handled by config in the class itself now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically everything in here was just lifted straight from LeftAndMain - though some small code quality improvements were made here-and-there.

Comment on lines +71 to +73
if (static::class === AdminController::class) {
throw new BadMethodCallException('getRequiredPermissions should be called on a subclass');
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Just to avoid it being called here, since this is explicitly abstract there's no permission for this class (or reason to want one)

Comment on lines +131 to +133
// LeftAndMain methods have a top-level uri access
if (static::class === LeftAndMain::class) {
$segment = '';
Copy link
Member Author

Choose a reason for hiding this comment

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

A follow-up card will make LeftAndMain abstract after removing the methods on it that are used from singleton - but for now this has to stay.

Comment on lines +161 to +184
public function redirect(string $url, int $code = 302): HTTPResponse
{
if ($this->getRequest()->isAjax()) {
$response = $this->getResponse();
$response->addHeader('X-ControllerURL', $url);
if ($this->getRequest()->getHeader('X-Pjax') && !$response->getHeader('X-Pjax')) {
$response->addHeader('X-Pjax', $this->getRequest()->getHeader('X-Pjax'));
}
$newResponse = new LeftAndMain_HTTPResponse(
$response->getBody(),
$response->getStatusCode(),
$response->getStatusDescription()
);
foreach ($response->getHeaders() as $k => $v) {
$newResponse->addHeader($k, $v);
}
$newResponse->setIsFinished(true);
$this->setResponse($newResponse);
// Actual response will be re-requested by client
return $newResponse;
} else {
return parent::redirect($url, $code);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not 100% sure exactly what all of the ramifications of this are - but since it's dealing explicitly with AJAX requests and that's mostly what this method is for, I decided to lift this out of LeftAndMain and into this class.

Comment on lines +189 to +203
public function getClientConfig(): array
{
// Allows the section name to be overridden in config
$name = static::config()->get('section_name') ?: static::class;
// Trim leading/trailing slash to make it easier to concatenate URL
// and use in routing definitions.
$url = trim($this->Link(), '/');
$clientConfig = [
'name' => $name,
'url' => $url,
'reactRoutePath' => preg_replace('/^' . preg_quote(AdminRootController::admin_url(), '/') . '/', '', $url),
];
$this->extend('updateClientConfig', $clientConfig);
return $clientConfig;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is lifted over here so that we can inject links for these controllers into the config that JS gets a hold of.

// Don't allow access if the request hasn't finished being handled and the user can't access this controller
if (!$this->canView() && !$this->getResponse()->isFinished()) {
// Allow subclasses and extensions to redirect somewhere more appropriate
$this->invokeWithExtensions('onInitPermissionFailure');
Copy link
Member Author

Choose a reason for hiding this comment

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

This lets LeftAndMain do some redirecting it was doing before without making the method abstract (and therefore required on classes that don't need it) and without doing a method_exists() check which always feels like the wrong move.

Comment on lines +234 to +248
// if no alternate menu items have matched, return a permission error
$messageSet = [
'default' => _t(
__CLASS__ . '.PERMDEFAULT',
"You must be logged in to access the administration area."
),
'alreadyLoggedIn' => _t(
__CLASS__ . '.PERMALREADY',
"I'm sorry, but you can't access that part of the CMS."
),
'logInAgain' => _t(
__CLASS__ . '.PERMAGAIN',
"You have been logged out of the CMS."
),
];
Copy link
Member Author

@GuySartorelli GuySartorelli Oct 16, 2024

Choose a reason for hiding this comment

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

Shortened these messages to suit the fact that most of the time these controllers will be handling AJAX requests and therefore there won't be a menu "below" like the old messages suggest.

I considered finding a way to use the longer messages in LeftAndMain specifically, but the context of "there's a form below" is unnecessary because... well, there's a form below. You can see it (or tab to it if you're on a screen reader)

Comment on lines +23 to +30
*/
private static string $url_base = 'admin';

/**
* The LeftAndMain child that will be used as the initial panel to display if none is selected (i.e. if you
* visit /admin)
*/
private static string $default_panel = SecurityAdmin::class;
Copy link
Member Author

Choose a reason for hiding this comment

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

These are just moved from below per our coding standards (i.e. properties before methods)

Comment on lines -76 to +71
$classes = CMSMenu::get_cms_classes(null, true, CMSMenu::URL_PRIORITY);
$classes = CMSMenu::get_cms_classes(AdminController::class, true, CMSMenu::URL_PRIORITY);
Copy link
Member Author

Choose a reason for hiding this comment

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

null defaults to LeftAndMain - we want the new controller as the base class.

Comment on lines -121 to +114
if (($arguments = $request->match($pattern, true)) !== false) {
if ($request->match($pattern, true) !== false) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Unrelated refactoring since that variable is never used.

@@ -22,6 +22,8 @@ class CMSProfileController extends LeftAndMain

private static $tree_class = Member::class;

private static $ignore_menuitem = true;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the config that lets us remove the code from _config.php

Comment on lines 254 to 275
private static $help_links = [
'CMS User help' => 'https://userhelp.silverstripe.org/en/5',
'Developer docs' => 'https://docs.silverstripe.org/en/5/',
'Community' => 'https://www.silverstripe.org/',
'Feedback' => 'https://www.silverstripe.org/give-feedback/',
];

/**
* The href for the anchor on the Silverstripe logo
*
* @config
* @var string
*/
private static $application_link = '//www.silverstripe.org/';

/**
* The application name
*
* @config
* @var string
*/
private static $application_name = 'Silverstripe';
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved from below, along with searchFilterCache as well. These were below some methods.

Comment on lines -315 to +304
$cmsClassNames = CMSMenu::get_cms_classes(LeftAndMain::class, true, CMSMenu::URL_PRIORITY);
$cmsClassNames = CMSMenu::get_cms_classes(AdminController::class, true, CMSMenu::URL_PRIORITY);
Copy link
Member Author

Choose a reason for hiding this comment

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

The config itself can be on any AdminController now.

@@ -91,7 +89,7 @@ public function activate(HTTPRequest $request): HTTPResponse
return $this->jsonResponse([
'result' => false,
'message' => _t(__CLASS__ . '.INVALID', 'Incorrect password'),
]);
], 400);
Copy link
Member Author

Choose a reason for hiding this comment

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

Unrelated refactoring - this is an error, so should have an error response code! There's no context in the original PR that created this class for why it was a 200. It looks like it was an oversight.

Copy link
Member

Choose a reason for hiding this comment

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

This should be 401, context for this is getting password wrong when trying to activate sudo mode.

https://stackoverflow.com/a/32752617

Copy link
Member Author

Choose a reason for hiding this comment

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

I hate HTTP response codes lol.
Updated to 401

}
}

public static function provideJsonSuccess(): array
Copy link
Member Author

Choose a reason for hiding this comment

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

This and everything below it was pulled from LeftAndMainTest.

Comment on lines -83 to +77
$admin = $this->objFromFixture(Member::class, 'admin');
$this->logInAs($admin);
$this->logInWithPermission('ADMIN');
Copy link
Member Author

Choose a reason for hiding this comment

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

The admin fixture and the logInWithPermission('ADMIN') user have the same permissions, so no need for the fixture.

@@ -135,65 +128,6 @@ public function testLeftAndMainSubclasses()
$this->assertMatchesRegularExpression('/<body[^>]*>/i', $response->getBody(), "$link should contain <body> tag");
}

public function testCanView()
Copy link
Member Author

Choose a reason for hiding this comment

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

This was testing the wrong thing - it was trying to test canView() checks by seeing what's in the menu. But the menu actually shouldn't contain CMSProfileController so this test was passing when it should have failed.

Moved canView check to the new test class, and now actually testing canView instead of the menu.

The menu itself is already tested in this class.

@GuySartorelli GuySartorelli marked this pull request as ready for review October 16, 2024 00:25
@@ -91,7 +89,7 @@ public function activate(HTTPRequest $request): HTTPResponse
return $this->jsonResponse([
'result' => false,
'message' => _t(__CLASS__ . '.INVALID', 'Incorrect password'),
]);
], 400);
Copy link
Member

Choose a reason for hiding this comment

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

This should be 401, context for this is getting password wrong when trying to activate sudo mode.

https://stackoverflow.com/a/32752617


/**
* Holds an array of url_pattern => controller k/v pairs, the same as Director::rules. However this is built
* dynamically from introspecting on all the classes that derive from LeftAndMain.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* dynamically from introspecting on all the classes that derive from LeftAndMain.
* dynamically from introspecting on all the classes that derive from AdminController.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

return;
}

$this->extend('onInit');
Copy link
Member

Choose a reason for hiding this comment

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

Weird place for an 'onInit' hook - seems like it should be at the bottom? Any reason why it's here? Does the implementation in LeftAndMain not work right if this hook is at the bottom?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm just keeping it in the same logic place it was with LeftAndMain. I don't know if it would break anything if we move it, and I don't particularly want to find out. Chances are if it breaks anything to move it, we won't find out for a year or so.
I'm guessing it's intentionally placed and that it's specifically not called onAfterInit() for that reason, though I can't confirm that.

Safer to just leave it where it is. But I can move it if you feel strongly.

Comment on lines 255 to 256
'CMS User help' => 'https://userhelp.silverstripe.org/en/5',
'Developer docs' => 'https://docs.silverstripe.org/en/5/',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'CMS User help' => 'https://userhelp.silverstripe.org/en/5',
'Developer docs' => 'https://docs.silverstripe.org/en/5/',
'CMS User help' => 'https://userhelp.silverstripe.org/en',
'Developer docs' => 'https://docs.silverstripe.org/en/',

Copy link
Member

Choose a reason for hiding this comment

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

Or /6/

Copy link
Member Author

Choose a reason for hiding this comment

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

We'll need to update a bunch of references like this, we should probably just do them all together. But I'll make this change to avoid ping pong.
Going with an explicit 6 so people don't get docs for 7 in their 6 sites when we get to that point.

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.

2 participants