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
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,14 @@ npm run format # Format code using WordPress standards
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
- `Enable_Caching.php` - Transient caching for query results
- `Exclude_Current.php` - Remove current post from results
- `Exclude_Posts.php` - Exclude specific posts by ID
- `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
- `OrderBy.php` - Post ordering with WP_Query compatibility normalization (e.g., 'id' → 'ID')
- `Post_Parent.php` - Child post filtering
- `Tax_Query.php` - Advanced taxonomy queries with AND/OR logic

Expand Down Expand Up @@ -150,6 +153,32 @@ Developers can extend AQL in two ways:

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

## Important Implementation Notes

### WP_Query Parameter Normalization

WordPress REST API and WP_Query handle parameter values differently:

- **REST API**: More lenient, automatically normalizes values (e.g., `orderby=id` → `orderby=ID`)
- **WP_Query**: Case-sensitive, requires exact values (e.g., must use `orderby=ID` not `orderby=id`)

This can cause discrepancies where queries work in the block editor (REST API) but fail on the frontend (WP_Query). The `OrderBy` trait handles this by normalizing `'id'` → `'ID'` to ensure consistent behavior across both contexts.

**When adding new orderby options:**
1. Check WordPress WP_Query documentation for the correct case/format
2. Add normalization in the `OrderBy` trait if the REST API and WP_Query values differ
3. Test both in the block editor (REST API) and on the frontend (WP_Query)

### Adding New Query Parameter Traits

To add a new query parameter type:

1. Create a new trait in `includes/Traits/` with a `process_{param_name}()` method
2. Add the trait to `Query_Params_Generator.php`
3. Add a mapping in `Query_Params_Generator::ALLOWED_CONTROLS` array
4. Create corresponding UI controls in `src/components/`
5. The trait's process method should populate `$this->custom_args` with WP_Query-compatible parameters

## Testing

PHPUnit tests are located in `tests/unit/`. Configuration in `phpunit.xml` uses PHPUnit 8.5 with Yoast polyfills for PHP 7.4+ compatibility.
Expand Down
48 changes: 48 additions & 0 deletions _blueprints/e2e-blueprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,54 @@
{
"step": "activatePlugin",
"pluginPath": "/wordpress/wp-content/plugins/advanced-query-loop/index.php"
},
{
"step": "runPHP",
"code": "<?php\nrequire_once '/wordpress/wp-load.php';\n$posts = get_posts(array('post_type' => 'post', 'posts_per_page' => -1, 'post_status' => 'any'));\nforeach ($posts as $post) {\n\twp_delete_post($post->ID, true);\n}\necho 'Deleted ' . count($posts) . ' existing posts';\n?>"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-06-15 14:30:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-02-10 09:15:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-09-22 16:45:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-01-05 08:00:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-11-30 11:20:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-03-18 13:00:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-07-07 15:30:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-04-25 12:15:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-12-12 10:45:00' --porcelain"
},
{
"step": "wp-cli",
"command": "wp post create --post_title='Test Post - ID: will be updated' --post_content='Test content for verifying Post ID ordering functionality.' --post_status=publish --post_date='2024-05-08 17:00:00' --porcelain"
},
{
"step": "runPHP",
"code": "<?php\nrequire_once '/wordpress/wp-load.php';\n\n// Update post titles to include their actual IDs\n$posts = get_posts(array(\n\t'post_type' => 'post',\n\t'posts_per_page' => -1,\n\t'post_status' => 'publish',\n\t'orderby' => 'ID',\n\t'order' => 'ASC'\n));\n\nforeach ($posts as $post) {\n\tif (strpos($post->post_title, 'will be updated') !== false) {\n\t\twp_update_post(array(\n\t\t\t'ID' => $post->ID,\n\t\t\t'post_title' => 'Test Post - ID: ' . $post->ID\n\t\t));\n\t}\n}\n\necho 'Cleaned and created ' . count($posts) . ' test posts';\n?>"
}
]
}
3 changes: 2 additions & 1 deletion includes/Query_Params_Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Query_Params_Generator {
use Traits\Tax_Query;
use Traits\Post_Parent;
use Traits\Enable_Caching;
use Traits\OrderBy;

/**
* The list of allowed controls and their associated params in the query.
Expand All @@ -30,7 +31,7 @@ class Query_Params_Generator {
'additional_post_types' => 'multiple_posts',
'taxonomy_query_builder' => 'tax_query',
'post_meta_query' => 'meta_query',
'post_order' => 'post_order',
'post_order' => 'orderBy',
'exclude_current_post' => 'exclude_current',
'include_posts' => 'include_posts',
'child_items_only' => 'post_parent',
Expand Down
71 changes: 71 additions & 0 deletions includes/Traits/OrderBy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
/**
* OrderBy Trait - Handles post ordering parameter normalization
*
* This trait processes the 'orderBy' parameter from the Query Loop block
* and normalizes it for WP_Query compatibility. This is necessary because
* the WordPress REST API and WP_Query handle certain parameter values
* differently.
*
* Key Issue Solved:
* - REST API (block editor): Accepts 'id' (lowercase) and normalizes internally
* - WP_Query (frontend): Requires 'ID' (uppercase) - case-sensitive
*
* Without this normalization, queries can work in the editor but fail on the
* frontend, causing posts to fall back to default date ordering instead of
* the selected ordering method.
*
* @package AdvancedQueryLoop\Traits
*/

namespace AdvancedQueryLoop\Traits;

/**
* Trait OrderBy
*
* Processes and normalizes the orderBy parameter from block attributes
* to ensure compatibility with WP_Query's case-sensitive requirements.
*
* @since 4.4.0
*/
trait OrderBy {
/**
* Process the orderBy parameter from the block query.
*
* This method retrieves the orderBy value from the block's custom parameters
* and normalizes it for WP_Query. Specifically, it handles the case where
* lowercase 'id' needs to be converted to uppercase 'ID'.
*
* WordPress WP_Query expects 'ID' (uppercase) for ordering by post ID, but
* the REST API accepts 'id' (lowercase). This normalization ensures consistent
* behavior between the block editor (which uses REST API) and the frontend
* (which uses WP_Query directly).
*
* Valid orderBy values (after normalization):
* - 'ID' - Order by post ID (note: uppercase required)
* - 'author' - Order by post author
* - 'title' - Order by post title
* - 'name' - Order by post name (slug)
* - 'date' - Order by post date
* - 'modified' - Order by last modified date
* - 'rand' - Random order
* - 'comment_count' - Order by number of comments
* - 'menu_order' - Order by menu order
* - 'post__in' - Order by post ID inclusion order
* - 'meta_value' - Order by meta value (requires meta_key)
* - 'meta_value_num' - Order by numeric meta value (requires meta_key)
*
* @since 4.4.0
*
* @return void
*/
// phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
public function process_orderBy(): void {
// Retrieve the orderBy parameter from the block's custom parameters.
$order_by = $this->custom_params['orderBy'] ?? null;

// Normalize lowercase 'id' to uppercase 'ID' for WP_Query compatibility.
// WP_Query is case-sensitive and only recognizes 'ID' (uppercase).
$this->custom_args['orderby'] = ( 'id' === $order_by ) ? 'ID' : $order_by;
}
}
Loading
Loading