Skip to content

Page Auto Complete not working correctly with OR-groups in selector string #1374

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

Closed
Toutouwai opened this issue May 4, 2021 · 8 comments
Closed

Comments

@Toutouwai
Copy link

Short description of the issue

I have this page structure:

2021-05-04_213701

And this selector string for selectable pages:

2021-05-04_213830

(I know this specific selector doesn't need to use OR-groups but this is just for demo purposes)

This works fine if my inputfield type is "Select":

2021-05-04_213949

But if I change the inputfield type to "Page Auto Complete" it seems that the selector string is ignored and any page in the site becomes selectable.

2021-05-04_214200

Setup/Environment

  • ProcessWire version: 3.0.177
@Toutouwai
Copy link
Author

Toutouwai commented May 4, 2021

I'm working around the problem using the suggestion here to hook ProcessPageSearch::findReady and replace a dummy selector string with the real selector string. It feels pretty hacky though.

As a general thing I don't understand why the selector string defined for the Page Reference field ever needs to pass through user input for PageAutocomplete fields. It seems to me that only the field and edited page need to be identified via the AJAX request and then PW can go to the field settings to securely get the selector string. This would avoid these kinds of issues.

@adrianbj
Copy link

adrianbj commented May 4, 2021

@ryancramerdesign - if you do dive into this, could you also please consider this (#550) at the same time ?

@ryancramerdesign
Copy link
Member

PageAutocomplete uses ProcessPageSearch as its AJAX source/endpoint, and the URL-based API for that is not built to support the full scope of everything that you can do in selectors, and it likely never will — it would be too complex and difficult to keep secure. Instead, I'd like to rewrite PageAutocomplete to use its own AJAX source eventually, but that's basically a full rewrite of the module and likely not going to happen soon. Now there's a good alternative. If you have the need to use things like OR-groups in ajax search results (or literally anything else), the new InputfieldTextTags can do it for Page fields, as you have full control over the selector since it's your own code that ultimately performs the find().

@Toutouwai
Copy link
Author

Toutouwai commented May 8, 2021

@ryancramerdesign, what I'm proposing doesn't require massive changes - just relatively simple logic added to ProcessPageSearch to get the selector directly from the Field settings when it receives a request from a PageAutocomplete inputfield that is associated with a Field.

As a demo I've done this using hooks as much as possible with only minimal changes to core code. Although if you were updating the core to accommodate this it would be even cleaner.

The core changes needed to make the below hooks work:

  1. Make InputfieldPageAutocomplete::getAjaxUrl hookable
  2. Make InputfieldPage::getFindPagesSelector public. Please consider making this method public regardless of if you want you want to adopt my proposal because it's a method that can be useful outside of the core (I've already needed to duplicate it in a module).
  3. Modify this line in InputfieldPageAutocomplete.js to:
var ajaxURL;
if($value.hasClass('has_field')) {
    ajaxURL = url + '&term=' + term;
} else {
    ajaxURL = url + '&' + searchField + operator + term;
}

Hooks:

$wire->addHookAfter('InputfieldPageAutocomplete::renderReadyHook', function(HookEvent $event) {
    /** @var InputfieldPageAutocomplete $inputfield */
    $inputfield = $event->object;
    $field = $inputfield->hasField;
    $page = $inputfield->hasPage;
    // Add class to inputfield so JS can identify that is associated with a Field
    if($field && $page) $inputfield->addClass('has_field');
});

$wire->addHookAfter('InputfieldPageAutocomplete::getAjaxUrl', function(HookEvent $event) {
    /** @var InputfieldPageAutocomplete $inputfield */
    $inputfield = $event->object;
    $field = $inputfield->hasField;
    $page = $inputfield->hasPage;
    // AJAX URL only needs to convey the field, page and search term
    // Selector and other field settings never pass through user input
    if($field && $page) $event->return = $event->wire()->config->urls->admin . "page/search/for?autocomplete=1&field={$field->id}&page={$page->id}";
});

$wire->addHookAfter('ProcessPageSearch::findReady', function(HookEvent $event) {
    $input = $event->wire()->input;
    $field_id = (int) $input->get('field');
    $page_id = (int) $input->get('page');
    // If this is an PageAutocomplete request associated with a Field...
    if($input->get('autocomplete') && $field_id && $page_id) {
        // Get the Field (in context in case it matters) and Page objects
        $field = $event->wire()->fields->get($field_id);
        $page = $event->wire()->pages->get($page_id);
        $field = $field->getContext($page);
        // Get selector directly from Field settings
        $selector = $field->findPagesSelector;
        // Do any "page." replacements
        $selector = InputfieldPage::getFindPagesSelector($page, $selector);
        // Get search term sanitized for use in selector
        $term = $input->get->selectorValue('term');
        // Get search fields string and add to selector
        $search_fields = $field->searchFields;
        if(strpos($search_fields, ' ') !== false) {
            $search_fields = implode('|', array_filter(explode(' ', $search_fields)));
        }
        $selector .= ", {$search_fields}{$field->operator}{$term}";
        // Add limit if no custom limit defined
        if(strpos($selector, 'limit=') === false) $selector .= ', limit=25';
        $event->return = $selector;
    }
});

It seems to me that this is more secure because the selector and other Field settings never need to pass through user input, and it's also quite a bit simpler than needing to send the Field settings on a roundtrip to the inputfield and back again via URL parameters (which requires some translation back and forth due to the limits of what can be passed in a URL).

This would also solve issue #550 and avoid the need for workarounds like the ones discussed in issue #584

Things I haven't accounted for here but hopefully are not tricky:

  1. Multi-language because I don't know how that stuff works
  2. Other ways of defining selectable pages for a Page field that don't directly result in a selector string

@matjazpotocnik
Copy link
Collaborator

matjazpotocnik commented Aug 8, 2023

@Toutouwai, there were some updates for InputfieldPageAutocomplete, getAjaxUrl() is hookable since 3.0.223, issue 550 is closed now, getFindPagesSelector is now populateFindPagesSelector, but stil not public. How all this does affect this issue?

@ryancramerdesign
Copy link
Member

@matjazpotocnik @Toutouwai I think the OR groups actually may be supported now, as PageAutocomplete now does something very similar to what Toutouwai described above.

@matjazpotocnik
Copy link
Collaborator

@ryancramerdesign, I already tested, it's working here, but wanted confirmation from Robin, so that I don't miss something.

@Toutouwai
Copy link
Author

Thanks, I tested and OR-groups are working for PageAutocomplete now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants