Skip to content

Various changes to support refactoring CMS search filter functionality#11587

Merged
emteknetnz merged 1 commit intosilverstripe:6.0from
creative-commoners:pulls/6.0/refactor-sitetree-filter
Mar 5, 2025
Merged

Various changes to support refactoring CMS search filter functionality#11587
emteknetnz merged 1 commit intosilverstripe:6.0from
creative-commoners:pulls/6.0/refactor-sitetree-filter

Conversation

@GuySartorelli
Copy link
Copy Markdown
Member

@GuySartorelli GuySartorelli commented Jan 30, 2025

CMSMain had different search filter code paths than the grid field search filter, and there was a lot of duplication of code. This commit works alongside the PR in silverstripe/silverstripe-cms to unify those code paths

Issue

@GuySartorelli GuySartorelli marked this pull request as draft January 30, 2025 03:19
@GuySartorelli GuySartorelli changed the title API Make SearchContext::individualFieldSearch() protected. Various changes to support refactoring CMS search filter functionality Jan 31, 2025
@GuySartorelli GuySartorelli force-pushed the pulls/6.0/refactor-sitetree-filter branch 3 times, most recently from 04adae2 to 1183438 Compare February 11, 2025 02:03
@GuySartorelli GuySartorelli force-pushed the pulls/6.0/refactor-sitetree-filter branch 11 times, most recently from 4421628 to 613653c Compare February 27, 2025 21:25
@GuySartorelli GuySartorelli marked this pull request as ready for review February 28, 2025 02:17
Comment on lines +308 to +313
if ($controller) {
$this->restoreFormState();
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

$controller is optional - but needs to be available and set in this form for form state to be recoverable. This is technically a bugfix but in reality we only really run into it when setting up test forms.

Comment on lines +71 to +82
/**
* Gets a JSON schema representing a form.
*/
public function getSchema(HTTPRequest $request): HTTPResponse
{
$schemaID = $request->getURL();
$parts = $request->getHeader(FormSchema::SCHEMA_HEADER);
$data = FormSchema::singleton()->getMultipartSchema($parts, $schemaID, $this->form);
$response = HTTPResponse::create(json_encode($data));
$response->addHeader('Content-Type', 'application/json');
return $response;
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Works very similar to FormSchemaController in admin, but is needed in framework so GridField can call it.
I looked at whether FormSchemaController can rely on this but it would require refactoring beyond what I'm willing to do in this set of PRs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Maybe the bulk of this could be moved into FormSchema and then called from both here and FormSchemaController to reduce a little bit of code duplication - let me know if you want me to explore that. Wouldn't be much effort (if it's doable in a sensible way), but also low value.

Copy link
Copy Markdown
Member

@emteknetnz emteknetnz Mar 3, 2025

Choose a reason for hiding this comment

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

I wouldn't worry above it, the fact that FormSchema::singleton()->getMultipartSchema() exists is really the bit that reduces code duplication.

* @param GridField $gridfield
* @return string
*/
public function getSearchFieldSchema(GridField $gridField)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was largely duplicated so has been moved into SearchContextForm

* @param GridField $gridfield
* @return HTTPResponse
*/
public function getSearchFormSchema(GridField $gridField)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was largely duplicated so has been moved into SearchContextForm FormRequestHandler

* If getPlaceHolderText is empty, this object will be used to build the placeholder
* e.g. 'Search "My Data Object"'
*/
private function getPlaceHolder(object $obj): string
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved into the new SearchContextForm which has some special handling for getPlaceholderText()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Private so doesn't need to be deprecated

/*
* Append a prefix to search field names to prevent conflicts with other fields in the search form
*/
private function addSearchPrefixToFields(FieldList $fields): void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Handled by the new form instead

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Private so doesn't need to be deprecated

/**
* Update CSS classes for form fields, including nested inside composite fields
*/
private function updateFieldClasses(FieldList $fields): void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Handled by the new form instead

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Private so doesn't need to be deprecated

return $this;
}

public function __clone(): void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See usage in SearchContextForm - also just seems sensible, given FieldList already handles cloning.

}
}

private function removeSearchPrefixFromFields(FieldList $fields): void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Private so doesn't need to be deprecated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changes here are to accomodate cloning

$this->setValidator(clone $this->validator);
}
foreach ($this->fields as $field) {
$field->setForm($this);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this recursive? e.g. will field groups and composite fields ensure their descendants call $field->setForm() properly?

I'd kind of expect this to happen in the FieldList cloning you mentioned in the comment above, so I'm a little surprised to see it here

Copy link
Copy Markdown
Member Author

@GuySartorelli GuySartorelli Mar 3, 2025

Choose a reason for hiding this comment

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

FieldList does have some logic, CompositeField also has some logic. But the form being cloned is where the form should be set. Cloning a FieldList doesn't mean the form is changing. The FieldList doesn't even keep track of the form, for that matter.

Comment on lines +71 to +82
/**
* Gets a JSON schema representing a form.
*/
public function getSchema(HTTPRequest $request): HTTPResponse
{
$schemaID = $request->getURL();
$parts = $request->getHeader(FormSchema::SCHEMA_HEADER);
$data = FormSchema::singleton()->getMultipartSchema($parts, $schemaID, $this->form);
$response = HTTPResponse::create(json_encode($data));
$response->addHeader('Content-Type', 'application/json');
return $response;
}
Copy link
Copy Markdown
Member

@emteknetnz emteknetnz Mar 3, 2025

Choose a reason for hiding this comment

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

I wouldn't worry above it, the fact that FormSchema::singleton()->getMultipartSchema() exists is really the bit that reduces code duplication.

/**
* A form for using a search context in the CMS.
*
* Note that the form submission goes through to the index action of the controller by default,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* Note that the form submission goes through to the index action of the controller by default,
* Note that the form submission goes through to the index action of the controller by default,

Could you point me to the bit where the controller actually processes the search form submission, I'm not sure where it is

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See CMSMain::ListViewForm() and GridFieldFilterHeader::getManipulatedData()

$this->prepareSearchFieldsForCMS($searchFields);
parent::__construct($controller, $name, $searchFields);
$this->setHTMLID(ClassInfo::shortName($controller) . '_' . $name);
$this->disableSecurityToken();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we disabling this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cool, could you add that comment back in above it so others know why it's done

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

public function __construct(RequestHandler $controller, SearchContext $searchContext, $name = 'SearchForm')
{
$this->searchContext = $searchContext;
$searchFields = clone $searchContext->getSearchFields();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to clone the search fields here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Because otherwise prepareSearchFieldsForCMS() will alter the names of the fields in place, which will affect the functionality within the search context itself that relies on form fields with specific names.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, just add that in an inline comment above so future generation know why it's cloned

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

$this->removeSearchPrefixFromFields($form->Fields());
$form->loadDataFrom($values);

// Get the values from the form where possible, as form fields may transform the value for DB coparisons
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// Get the values from the form where possible, as form fields may transform the value for DB coparisons
// Get the values from the form where possible, as form fields may transform the value for DB comparisons

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

}

/**
* Get the schema for this search context for use in the CMS search filter form.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is really confusing having 2x schemas. Is there another way we could refer to this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This paradigm already exists, and they are both schemas... I can't think of another way to refer to it and don't really want to be reinventing things this close to the beta release.

*/
public function getSchemaData(): array
{
$schemaUrl = $this->getRequestHandler()->Link('schema');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could be called $formSchemaUrl instead? OK to keep as $schemaUrl if we no longer call the method getSchemaData() i.e. remove the word 'Schema' from it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated the variable name

@GuySartorelli GuySartorelli force-pushed the pulls/6.0/refactor-sitetree-filter branch from 613653c to 160d5f6 Compare March 3, 2025 21:16
public function __construct(RequestHandler $controller, SearchContext $searchContext, $name = 'SearchForm')
{
$this->searchContext = $searchContext;
$searchFields = clone $searchContext->getSearchFields();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, just add that in an inline comment above so future generation know why it's cloned

$this->prepareSearchFieldsForCMS($searchFields);
parent::__construct($controller, $name, $searchFields);
$this->setHTMLID(ClassInfo::shortName($controller) . '_' . $name);
$this->disableSecurityToken();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cool, could you add that comment back in above it so others know why it's done

@GuySartorelli GuySartorelli force-pushed the pulls/6.0/refactor-sitetree-filter branch from 160d5f6 to 913c74e Compare March 4, 2025 20:26
CMSMain had different search filter code paths than the grid field
search filter, and there was a lot of duplication of code. This commit
works alongside a commit in silverstripe/silverstripe-cms to unify those
code paths
@GuySartorelli GuySartorelli force-pushed the pulls/6.0/refactor-sitetree-filter branch from 913c74e to 35c5f26 Compare March 4, 2025 21:48
@GuySartorelli
Copy link
Copy Markdown
Member Author

Minor changes to SearchContext API here to make the elemental work easier.

@emteknetnz emteknetnz merged commit fd5c465 into silverstripe:6.0 Mar 5, 2025
12 checks passed
@emteknetnz emteknetnz deleted the pulls/6.0/refactor-sitetree-filter branch March 5, 2025 03:15
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