Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Advanced Query Loop (AQL) is a WordPress plugin that extends the core Query Loop block with advanced querying capabilities. It provides a block variation with additional controls for taxonomy queries, post meta queries, date queries, post ordering, and more.

The plugin uses a hybrid architecture combining PHP server-side logic with TypeScript/React for the block editor interface.

## Development Commands

### Setup
```bash
npm run setup # Install PHP dependencies (runs composer run dev)
composer run dev # Install PHP dev dependencies with autoload
```

### Development
```bash
npm start # Start development mode (TypeScript watch + webpack)
npm run start:webpack # Start webpack only
npm run start:hot # Start with hot module replacement
npm run tsc # Run TypeScript compiler once
npm run tsc:watch # Run TypeScript compiler in watch mode
```

### Building
```bash
npm run build # Build production JavaScript/TypeScript
npm run release # Full release build (composer build + npm build + plugin-zip)
npm run plugin-zip # Create distributable plugin ZIP
composer run build # Production PHP build (no dev dependencies)
```

### Testing
```bash
npm run test:unit # Run PHPUnit tests
./vendor/bin/phpunit # Run PHPUnit directly
```

### Local Environment
```bash
npm run wp-env start # Start local WordPress environment
npm run wp-env stop # Stop local WordPress environment
```

### Code Quality
```bash
npm run format # Format code using WordPress standards
```

## Architecture

### PHP Architecture (Server-Side)

**Entry Point**: `index.php` - Main plugin file that loads the autoloader.

**Core Query Processing**:
- `includes/Query_Params_Generator.php` - Central class that processes all custom query parameters using traits
- `includes/query-loop.php` - Hooks into WordPress filters to modify queries on both frontend and REST API
- Uses the `pre_render_block` filter to intercept Query Loop blocks with `namespace: 'advanced-query-loop'`
- Uses `query_loop_block_query_vars` filter for non-inherited queries
- Uses `rest_{post_type}_query` filters to enable custom parameters in the block editor

**Query Parameter Traits** (`includes/Traits/`):
Each trait in the Traits directory handles a specific query modification:
- `Date_Query.php` - Before/after/relative date filtering
- `Disable_Pagination.php` - Performance optimization by disabling pagination
- `Exclude_Current.php` - Remove current post from results
- `Exclude_Taxonomies.php` - Exclude posts by taxonomy terms
- `Include_Posts.php` - Manually select specific posts
- `Meta_Query.php` - Post meta filtering with multiple conditions
- `Multiple_Posts.php` - Multiple post type selection
- `Post_Parent.php` - Child post filtering
- `Tax_Query.php` - Advanced taxonomy queries with AND/OR logic

**Key Filter Hook**: `aql_query_vars` - This filter allows extensions to modify query arguments. It receives:
1. `$query_args` - Arguments to be passed to WP_Query
2. `$block_query` - The query attribute from the block
3. `$inherited` - Whether the query is being inherited from template

### TypeScript/React Architecture (Block Editor)

**Entry Point**: `src/variations/index.ts` - Registers the block variation and exports SlotFills.

**Block Variation Registration**: Registers `advanced-query-loop` as a variation of `core/query` with custom namespace attribute.

**Controls System** (`src/variations/controls.tsx`):
- Uses `addFilter` on `editor.BlockEdit` to inject custom controls
- Conditionally renders different control sets based on `query.inherit` attribute
- When `inherit: false` - Shows all advanced controls in the "Advanced Query Settings" panel
- When `inherit: true` - Shows limited controls (only PostOrderControls and inherited query slot)

**UI Components** (`src/components/`):
Each component corresponds to a query feature:
- `post-meta-query-controls.js` - Complex meta query builder
- `post-date-query-controls.js` - Date filtering UI
- `multiple-post-select.js` - Post type selector
- `post-order-controls.tsx` - Order/orderby controls
- `post-exclude-controls.js` - Exclude current post, categories
- `taxonomy-query-control.js` - Advanced taxonomy query builder
- `post-include-controls.js` - Manual post selection
- `pagination-toggle.js` - Enable/disable pagination
- `child-items-toggle.js` - Show only child items

**SlotFill System** (`src/slots/`):
Extensibility mechanism exposed via `window.aql`:
- `AQLControls` - Slot for controls shown when NOT inheriting query
- `AQLControlsInheritedQuery` - Slot for controls shown when inheriting query
- `AQLLegacyControls` - Slot for legacy Gutenberg < 19 controls

### Build System

**Webpack Configuration** (`webpack.config.js`):
- Extends `@wordpress/scripts` default configuration
- Entry point: `src/variations/index.ts`
- Additional entry for legacy pre-GB-19 controls: `src/legacy-controls/pre-gb-19.js`
- Exports library to global `window.aql`
- Dev server allows all hosts for cross-environment testing

**TypeScript Configuration**:
- Strict mode enabled with `noUncheckedIndexedAccess`
- Target: ES2022
- No emit (webpack handles compilation)
- Preserves JSX and modules

### Data Flow

1. **In Block Editor**:
- User interacts with React controls in Inspector panel
- Controls update block attributes via `setAttributes`
- Attributes stored in block's `query` object with custom properties
- REST API requests use `rest_{post_type}_query` filters to preview results
- `Query_Params_Generator` processes custom params into WP_Query format

2. **On Frontend**:
- `pre_render_block` filter catches blocks with `namespace: 'advanced-query-loop'`
- For inherited queries: Modifies global `$wp_query` directly
- For non-inherited queries: Hooks into `query_loop_block_query_vars`
- `Query_Params_Generator` converts block attributes to WP_Query args
- `aql_query_vars` filter allows final modifications before query execution

### Extensibility

Developers can extend AQL in two ways:

1. **JavaScript SlotFills**: Add custom controls via `window.aql.AQLControls` or `window.aql.AQLControlsInheritedQuery`
2. **PHP Filter Hook**: Modify query arguments via `aql_query_vars` filter

See `extending-aql.md` for detailed examples.

## Testing

PHPUnit tests are located in `tests/unit/`. Configuration in `phpunit.xml` uses PHPUnit 8.5 with Yoast polyfills for PHP 7.4+ compatibility.

## Plugin Distribution

The plugin follows WordPress.org conventions:
- `readme.txt` - WordPress.org plugin readme
- `readme.md` - GitHub readme
- Main branch: `trunk`
- PHP namespace: `AdvancedQueryLoop\`
- Text domain: `advanced-query-loop`
73 changes: 38 additions & 35 deletions _blueprints/cpt-demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,38 @@
* @package AdvancedQueryLoop
*/

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

/**
* Register the post type
*/
function register_stream_post_type() {
$labels = array(
'name' => _x( 'Streams', 'Post type general name', 'twitch-theme' ),
'singular_name' => _x( 'Stream', 'Post type singular name', 'twitch-theme' ),
'menu_name' => _x( 'Streams', 'Admin Menu text', 'twitch-theme' ),
'name_admin_bar' => _x( 'Stream', 'Add New on Toolbar', 'twitch-theme' ),
'add_new' => __( 'Add New', 'twitch-theme' ),
'add_new_item' => __( 'Add New Stream', 'twitch-theme' ),
'new_item' => __( 'New Stream', 'twitch-theme' ),
'edit_item' => __( 'Edit Stream', 'twitch-theme' ),
'view_item' => __( 'View Stream', 'twitch-theme' ),
'all_items' => __( 'All Stream', 'twitch-theme' ),
'search_items' => __( 'Search Streams', 'twitch-theme' ),
'parent_item_colon' => __( 'Parent Stream:', 'twitch-theme' ),
'not_found' => __( 'No Streams found.', 'twitch-theme' ),
'not_found_in_trash' => __( 'No Streams found in Trash.', 'twitch-theme' ),
'featured_image' => _x( 'Stream Thumbnail', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'twitch-theme' ),
'set_featured_image' => _x( 'Set stream thumbnail', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'twitch-theme' ),
'remove_featured_image' => _x( 'Remove stream thumbnail', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'twitch-theme' ),
'use_featured_image' => _x( 'Use as stream thumbnail', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'twitch-theme' ),
'archives' => _x( 'Stream archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'twitch-theme' ),
'insert_into_item' => _x( 'Insert into Stream', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'twitch-theme' ),
'uploaded_to_this_item' => _x( 'Uploaded to this Stream', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'twitch-theme' ),
'filter_items_list' => _x( 'Filter Stream list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'twitch-theme' ),
'items_list_navigation' => _x( 'Streams list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'twitch-theme' ),
'items_list' => _x( 'Streams list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'twitch-theme' ),
'name' => _x( 'Streams', 'Post type general name', 'advanced-query-loop' ),
'singular_name' => _x( 'Stream', 'Post type singular name', 'advanced-query-loop' ),
'menu_name' => _x( 'Streams', 'Admin Menu text', 'advanced-query-loop' ),
'name_admin_bar' => _x( 'Stream', 'Add New on Toolbar', 'advanced-query-loop' ),
'add_new' => __( 'Add New', 'advanced-query-loop' ),
'add_new_item' => __( 'Add New Stream', 'advanced-query-loop' ),
'new_item' => __( 'New Stream', 'advanced-query-loop' ),
'edit_item' => __( 'Edit Stream', 'advanced-query-loop' ),
'view_item' => __( 'View Stream', 'advanced-query-loop' ),
'all_items' => __( 'All Stream', 'advanced-query-loop' ),
'search_items' => __( 'Search Streams', 'advanced-query-loop' ),
'parent_item_colon' => __( 'Parent Stream:', 'advanced-query-loop' ),
'not_found' => __( 'No Streams found.', 'advanced-query-loop' ),
'not_found_in_trash' => __( 'No Streams found in Trash.', 'advanced-query-loop' ),
'featured_image' => _x( 'Stream Thumbnail', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'advanced-query-loop' ),
'set_featured_image' => _x( 'Set stream thumbnail', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'advanced-query-loop' ),
'remove_featured_image' => _x( 'Remove stream thumbnail', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'advanced-query-loop' ),
'use_featured_image' => _x( 'Use as stream thumbnail', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'advanced-query-loop' ),
'archives' => _x( 'Stream archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'advanced-query-loop' ),
'insert_into_item' => _x( 'Insert into Stream', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'advanced-query-loop' ),
'uploaded_to_this_item' => _x( 'Uploaded to this Stream', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'advanced-query-loop' ),
'filter_items_list' => _x( 'Filter Stream list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'advanced-query-loop' ),
'items_list_navigation' => _x( 'Streams list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'advanced-query-loop' ),
'items_list' => _x( 'Streams list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'advanced-query-loop' ),
);

$args = array(
Expand Down Expand Up @@ -90,17 +93,17 @@ function register_stream_post_type() {
);

$labels = array(
'name' => _x( 'Genres', 'taxonomy general name', 'textdomain' ),
'singular_name' => _x( 'Genre', 'taxonomy singular name', 'textdomain' ),
'search_items' => __( 'Search Genres', 'textdomain' ),
'all_items' => __( 'All Genres', 'textdomain' ),
'parent_item' => __( 'Parent Genre', 'textdomain' ),
'parent_item_colon' => __( 'Parent Genre:', 'textdomain' ),
'edit_item' => __( 'Edit Genre', 'textdomain' ),
'update_item' => __( 'Update Genre', 'textdomain' ),
'add_new_item' => __( 'Add New Genre', 'textdomain' ),
'new_item_name' => __( 'New Genre Name', 'textdomain' ),
'menu_name' => __( 'Genre', 'textdomain' ),
'name' => _x( 'Genres', 'taxonomy general name', 'advanced-query-loop' ),
'singular_name' => _x( 'Genre', 'taxonomy singular name', 'advanced-query-loop' ),
'search_items' => __( 'Search Genres', 'advanced-query-loop' ),
'all_items' => __( 'All Genres', 'advanced-query-loop' ),
'parent_item' => __( 'Parent Genre', 'advanced-query-loop' ),
'parent_item_colon' => __( 'Parent Genre:', 'advanced-query-loop' ),
'edit_item' => __( 'Edit Genre', 'advanced-query-loop' ),
'update_item' => __( 'Update Genre', 'advanced-query-loop' ),
'add_new_item' => __( 'Add New Genre', 'advanced-query-loop' ),
'new_item_name' => __( 'New Genre Name', 'advanced-query-loop' ),
'menu_name' => __( 'Genre', 'advanced-query-loop' ),
);

$args = array(
Expand Down
2 changes: 2 additions & 0 deletions includes/enqueues.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

use function AdvancedQueryLoop\Utils\{ is_gutenberg_plugin_version_or_higher, is_core_version_or_higher };

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

// Bail on unit tests.
if ( ! function_exists( 'add_action' ) ) {
Expand Down
3 changes: 3 additions & 0 deletions includes/query-loop.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace AdvancedQueryLoop;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

// Bail on unit tests.
if ( ! function_exists( 'add_filter' ) ) {
return;
Expand Down
3 changes: 3 additions & 0 deletions includes/taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace AdvancedQueryLoop\Taxonomy;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

function convert_names_to_ids( $names, $tax ) {
$rtn = [];
foreach ( $names as $name ) {
Expand Down
3 changes: 3 additions & 0 deletions includes/utilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace AdvancedQueryLoop\Utils;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

/**
* Helper to determine if the Gutenberg plugin is installed and if so, if it is at or higher a given version.
*
Expand Down
2 changes: 1 addition & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Contributors: welcher
Tags: Query Loop, Custom Queries, Advanced Queries, Post Meta, Taxonomy
Requires at least: 6.2
Tested up to: 6.8.1
Tested up to: 6.9
Stable tag: 4.3.0
Requires PHP: 7.4
License: GPL v2 or later
Expand Down
Loading