# Advanced Search Widget

> Searchable input that filters multiple post types live via AJAX. Configurable result template (title / image / excerpt / link). Optional post-type filter dropdown.

**Class file:** `includes/Elements/Advanced_Search.php` (2,381 lines)
**Slug:** `advanced-search` (widget id `eael-advanced-search`)
**Public docs:** <https://essential-addons.com/elementor/docs/advanced-search/>
**Pro-shared:** Pro-only. Uses Pro's `Helper`.

## Overview

Live-search input — user types a query, AJAX returns matching posts as a dropdown of result cards. User can filter by post type (single or multi). Result rendering customizable (title + image + excerpt + meta + link). Common use: site-wide search bar in headers, catalogue browsers.

## Pro vs Lite

Pro-only.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Advanced_Search.php` | Widget class (2,381 lines) |
| `src/css/view/advanced-search.scss` → `assets/front-end/css/view/advanced-search.min.css` | Search input + results dropdown styling |
| `src/js/view/advanced-search.js` → `assets/front-end/js/view/advanced-search.min.js` | Debounced AJAX + result render |
| `config.php` entry `'advanced-search'` | Self CSS + self JS |

## Architecture

- **Composes Pro's `Helper`** (line 16).
- **AJAX live search** — debounced input → `wp_ajax_*` (likely registered in Lite's `Ajax_Handler`; verify) → returns rendered HTML or JSON results.
- **Post-type discovery** — line 2079: `get_post_types( ['public' => true], 'objects' )` enumerates public post types for the filter dropdown.
- **2,381 lines** — extensive controls: per-result-card styling, per-region typography, dropdown positioning, no-results message styling.

## Render Output

```html
<div class="eael-advanced-search-wrap">
  [?] <select class="eael-search-filter">
    <option value="any">All</option>
    <option value="post">Posts</option>
    <option value="page">Pages</option>
    ...
  </select>
  <input type="search" class="eael-search-input" placeholder="{placeholder}" />
  <button class="eael-search-submit"><i class="fa-search"></i></button>
  <!-- Results dropdown (populated via AJAX) -->
  <div class="eael-search-results">
    <div class="eael-search-result-item">
      [?] <img src="{thumbnail}" />
      <div>
        <h4><a href="{permalink}">{title}</a></h4>
        [?] <p class="excerpt">{excerpt}</p>
        [?] <span class="meta">{post_type} · {date}</span>
      </div>
    </div>
    ...
    [?] <div class="eael-no-results">No matches</div>
  </div>
</div>
```

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| Placeholder text | Content → Input | TEXT | Search-input placeholder |
| Post-type filter enable | Content → Filter | SWITCHER | Show filter dropdown |
| Allowed post types | Content → Filter | SELECT2 | Multi-select |
| Search trigger | Content → Behavior | CHOOSE | `live` (debounced) / `on_submit` |
| Debounce delay | Content → Behavior | NUMBER | ms |
| Per-result render toggles (image / excerpt / meta) | Content → Result | SWITCHER | Per-element visibility |
| Submit button (icon, text, position) | Content → Button | various | CTA |
| Per-region styling | Style → ... | various | Input / dropdown / result-card styling |

## Conditional Dependencies

```text
filter_enable = 'yes' → post-type SELECT2 visible
search_trigger = 'live' → debounce_delay visible
result_show_image / excerpt / meta → per-region style controls
```

## JavaScript Lifecycle

```js
var AdvSearchHandler = function( $scope, $ ) {
    var $input = $scope.find( '.eael-search-input' );
    var $filter = $scope.find( '.eael-search-filter' );
    var $results = $scope.find( '.eael-search-results' );
    var debounce;
    $input.on( 'input', function() {
        clearTimeout( debounce );
        debounce = setTimeout( function() {
            $.post( eael.ajaxurl, {
                action: 'eael_advanced_search',
                nonce: eael.nonce,
                query: $input.val(),
                post_type: $filter.val()
            }, function( resp ) {
                $results.html( resp.data.html );
            } );
        }, debounceDelay );
    } );
};
```

## Hooks & Filters

### AJAX endpoint

Registered in Lite's `Ajax_Handler` (verify exact action name). Same nonce-hardening rules apply — see [`.claude/skills/nopriv-ajax-hardening/SKILL.md`](../../.claude/skills/nopriv-ajax-hardening/SKILL.md).

## Common Issues

| Symptom | Cause | Fix |
| --- | --- | --- |
| No results returned | AJAX endpoint missing / nonce mismatch | Network: inspect AJAX response |
| Slow search | Debounce too short | Increase `debounce_delay` |
| Filter dropdown empty | No public post types registered | Add CPTs or remove filter |
| XSS via excerpt | Excerpt not escaped server-side | Verify `wp_kses_post` on excerpt in AJAX response |
| Results show private / draft posts | Caller-controlled `post_status` not stripped | Apply nopriv-hardening pattern: strip + redefault to `publish` |

## Known Limitations

- **2,381 lines** — heavy controls
- **No relevance scoring** — `WP_Query` `s` parameter is keyword-LIKE; no full-text or relevance
- **No search index integration** (ElasticPress / Algolia / Relevanssi)
- **No analytics / tracking** — popular searches not surfaced
- **Result template not user-customizable** beyond toggles — no template-file dispatch
- **Public post types only** by default — private CPTs require manual allowlist

## Cross-References

- Skill: [`/nopriv-ajax-hardening`](../../.claude/skills/nopriv-ajax-hardening/SKILL.md) — verify AJAX endpoint hardening
- Related: [`post-list.md`](post-list.md) (AJAX search input is similar)
- Shared patterns: [`_patterns.md`](_patterns.md)
