diff --git a/README.md b/README.md index 8aa6c4f16..17168698d 100644 --- a/README.md +++ b/README.md @@ -3150,7 +3150,7 @@ wp post get [--field=] [--fields=] [--format=] Gets a list of posts. ~~~ -wp post list [--=] [--p=|ID] [--title=|post_title] [--name=<slug>|post_name] [--author=<author>|post_author] [--author_name=<author_name>] [--post_type=<post_type>] [--post_status=<post_status>] [--post_parent=<post_parent>] [--post_mime_type=<post_mime_type>] [--menu_order=<menu_order>] [--comment_status=<comment_status>] [--ping_status=<ping_status>] [--comment_count=<comment_count>] [--s=<string>] [--year=<year>] [--monthnum=<monthnum>] [--day=<day>] [--m=<yearmonth>] [--w=<week>] [--field=<field>] [--fields=<fields>] [--format=<format>] +wp post list [--<field>=<value>] [--p=<id>|ID] [--title=<title>|post_title] [--name=<slug>|post_name] [--author=<author>|post_author] [--author_name=<author_name>] [--cat=<cat>] [--category_name=<category_name>] [--tag=<tag>] [--post_type=<post_type>] [--post_status=<post_status>] [--post_parent=<post_parent>] [--post_mime_type=<post_mime_type>] [--menu_order=<menu_order>] [--comment_status=<comment_status>] [--ping_status=<ping_status>] [--comment_count=<comment_count>] [--s=<string>] [--year=<year>] [--monthnum=<monthnum>] [--day=<day>] [--hour=<hour>] [--minute=<minute>] [--second=<second>] [--m=<yearmonth>] [--w=<week>] [--meta_key=<meta_key>] [--meta_value=<meta_value>] [--orderby=<orderby>] [--order=<order>] [--post__in=<ids>] [--post__not_in=<ids>] [--post_name__in=<slugs>] [--post_parent__in=<ids>] [--post_parent__not_in=<ids>] [--author__in=<ids>] [--author__not_in=<ids>] [--category__in=<ids>] [--category__and=<ids>] [--category__not_in=<ids>] [--tag_id=<tag_id>] [--tag__in=<ids>] [--tag__and=<ids>] [--tag__not_in=<ids>] [--tag_slug__in=<slugs>] [--tag_slug__and=<slugs>] [--page_id=<page_id>] [--pagename=<pagename>] [--attachment_id=<attachment_id>] [--date_query=<json>] [--meta_query=<json>] [--tax_query=<json>] [--meta_compare=<meta_compare>] [--sentence=<sentence>] [--exact=<exact>] [--search_columns=<columns>] [--perm=<perm>] [--posts_per_page=<number>] [--paged=<paged>] [--offset=<offset>] [--nopaging=<nopaging>] [--field=<field>] [--fields=<fields>] [--format=<format>] ~~~ Display posts based on all arguments supported by [WP_Query()](https://developer.wordpress.org/reference/classes/wp_query/). @@ -3186,6 +3186,17 @@ Only shows post types marked as post by default. [--author_name=<author_name>] Filter by the 'user_nicename' of the post's author. + [--cat=<cat>] + Filter by category ID. Accepts a comma-separated list to match any of + them, and a negative ID excludes that category instead. + + [--category_name=<category_name>] + Filter by category slug. + + [--tag=<tag>] + Filter by tag slug. Accepts a comma-separated list to match any of them, + or a '+'-separated list to require all of them. + [--post_type=<post_type>] Filter by post type. Defaults to 'post'. Accepts a comma-separated list, or 'any' for every type registered without 'exclude_from_search'. @@ -3225,12 +3236,137 @@ Only shows post types marked as post by default. [--day=<day>] Filter by day of the month, 1 to 31. + [--hour=<hour>] + Filter by hour, 0 to 23. + + [--minute=<minute>] + Filter by minute, 0 to 59. + + [--second=<second>] + Filter by second, 0 to 59. + [--m=<yearmonth>] Filter by year and month together, e.g. 202401. [--w=<week>] Filter by week of the year, 0 to 53. + [--meta_key=<meta_key>] + Filter by posts having this meta key. Pair it with `--meta_value` to + filter on the value as well. + + [--meta_value=<meta_value>] + Filter by this meta value. Needs `--meta_key` to say which key it + belongs to. + + [--orderby=<orderby>] + Order the results by this field. Accepts what WP_Query accepts, e.g. + 'date', 'title', 'ID', 'menu_order', 'rand', or 'meta_value' alongside + `--meta_key`. + + [--order=<order>] + Direction to order by. Accepts 'ASC' or 'DESC'. + + [--post__in=<ids>] + Only list the posts with these IDs (comma-separated). + + [--post__not_in=<ids>] + Exclude the posts with these IDs (comma-separated). + + [--post_name__in=<slugs>] + Only list the posts with these slugs (comma-separated). Unlike `--name` + this is not a single-post query, so it reaches drafts as well. + + [--post_parent__in=<ids>] + Only list the posts whose parent is one of these IDs (comma-separated). + + [--post_parent__not_in=<ids>] + Exclude the posts whose parent is one of these IDs (comma-separated). + + [--author__in=<ids>] + Only list the posts by these author IDs (comma-separated). + + [--author__not_in=<ids>] + Exclude the posts by these author IDs (comma-separated). + + [--category__in=<ids>] + Only list the posts in these category IDs (comma-separated). + + [--category__and=<ids>] + Only list the posts in all of these category IDs (comma-separated). + + [--category__not_in=<ids>] + Exclude the posts in these category IDs (comma-separated). + + [--tag_id=<tag_id>] + Filter by tag ID. + + [--tag__in=<ids>] + Only list the posts with these tag IDs (comma-separated). + + [--tag__and=<ids>] + Only list the posts with all of these tag IDs (comma-separated). + + [--tag__not_in=<ids>] + Exclude the posts with these tag IDs (comma-separated). + + [--tag_slug__in=<slugs>] + Only list the posts with these tag slugs (comma-separated). + + [--tag_slug__and=<slugs>] + Only list the posts with all of these tag slugs (comma-separated). + + [--page_id=<page_id>] + Filter by page ID. Needs `--post_type=page` to match anything. + + [--pagename=<pagename>] + Filter by page slug. Needs `--post_type=page` to match anything. + + [--attachment_id=<attachment_id>] + Filter by attachment ID. Needs `--post_type=attachment` to match + anything. + + [--date_query=<json>] + Filter by a date query, given as JSON. See WP_Date_Query. + + [--meta_query=<json>] + Filter by a meta query, given as JSON. See WP_Meta_Query. + + [--tax_query=<json>] + Filter by a taxonomy query, given as JSON. See WP_Tax_Query. + + [--meta_compare=<meta_compare>] + Operator to test `--meta_value` with, e.g. '=', '!=', '>' or 'LIKE'. + + [--sentence=<sentence>] + Match `--s` as one phrase rather than as separate words. Accepts 1 or 0. + + [--exact=<exact>] + Match `--s` against the whole column rather than part of it. Accepts 1 + or 0. + + [--search_columns=<columns>] + Comma-separated list of columns `--s` looks in. Accepts 'post_title', + 'post_excerpt' and 'post_content'. Needs WordPress 6.2 or later. + + [--perm=<perm>] + Filter by what the current user may do with the post. Accepts + 'readable' or 'editable'; pair it with the global `--user` argument, + since WP-CLI is no user by default. + + [--posts_per_page=<number>] + How many posts to return. Defaults to -1, meaning every post. + + [--paged=<paged>] + Which page of results to return, counted in `--posts_per_page` steps. + + [--offset=<offset>] + How many posts to skip. Needs `--posts_per_page` set to something other + than -1, which otherwise takes precedence. + + [--nopaging=<nopaging>] + Return every post, ignoring `--posts_per_page`. Accepts 1 or 0. + [--field=<field>] Prints the value of a single field for each post. diff --git a/features/post.feature b/features/post.feature index 7d7571e1b..1cf5f9014 100644 --- a/features/post.feature +++ b/features/post.feature @@ -688,3 +688,193 @@ Feature: Manage WordPress posts """ {DOOMED_ID} """ + + Scenario: Filtering by category and tag + When I run `wp term create category 'Alpha Cat' --porcelain` + Then save STDOUT as {CAT_ID} + + When I run `wp term create post_tag 'Alpha Tag' --slug=alpha-tag --porcelain` + Then save STDOUT as {TAG_ID} + + When I run `wp post create --post_title='Tagged' --post_status=publish --post_category={CAT_ID} --tags_input=alpha-tag --porcelain` + Then STDOUT should be a number + + When I run `wp post list --cat={CAT_ID} --format=count` + Then STDOUT should be: + """ + 1 + """ + + # A negative ID excludes that category rather than selecting it. + When I run `wp post list --cat=-{CAT_ID} --field=post_title` + Then STDOUT should not contain: + """ + Tagged + """ + + When I run `wp post list --tag=alpha-tag --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --category_name=alpha-cat --format=count` + Then STDOUT should be: + """ + 1 + """ + + Scenario: Filtering by meta, time of day and order + When I run `wp post create --post_title='Timed' --post_status=publish --post_date='2020-03-04 05:06:07' --porcelain` + Then STDOUT should be a number + And save STDOUT as {TIMED_ID} + + When I run `wp post meta add {TIMED_ID} color blue` + Then STDOUT should not be empty + + When I run `wp post create --post_title='Locked' --post_status=publish --post_password=secret --porcelain` + Then STDOUT should be a number + + # Time of day, alongside the year/month/day filters already documented. + # Two units at once exercises the combined path; all three of hour, minute + # and second together is left out on purpose, because that path compares a + # DATE_FORMAT() string and the SQLite integration plugin does not emulate + # it the way MySQL does, so it matches nothing there. + When I run `wp post list --hour=5 --minute=6 --field=post_title` + Then STDOUT should be: + """ + Timed + """ + + When I run `wp post list --second=7 --field=post_title` + Then STDOUT should be: + """ + Timed + """ + + # A meta key on its own, then narrowed by its value. + When I run `wp post list --meta_key=color --field=post_title` + Then STDOUT should be: + """ + Timed + """ + + When I run `wp post list --meta_key=color --meta_value=red --format=count` + Then STDOUT should be: + """ + 0 + """ + + When I run `wp post list --orderby=title --order=ASC --field=post_title` + Then STDOUT should be: + """ + Hello world! + Locked + Timed + """ + + When I run `wp post list --orderby=title --order=DESC --field=post_title` + Then STDOUT should be: + """ + Timed + Locked + Hello world! + """ + + Scenario: Filtering by lists and JSON queries + When I run `wp post create --post_title='Zebra Title' --post_content='nothing' --post_status=publish --porcelain` + Then save STDOUT as {TITLED} + + When I run `wp post create --post_title='Plain' --post_name=plain --post_content='zebra in body' --post_status=publish --porcelain` + Then save STDOUT as {BODIED} + + When I run `wp post meta add {TITLED} rank 5` + Then STDOUT should not be empty + + # Arguments carrying '__' are split on commas before they reach WP_Query, + # which is what makes them usable from the command line at all. + When I run `wp post list --post__in={TITLED},{BODIED} --format=count` + Then STDOUT should be: + """ + 2 + """ + + When I run `wp post list --post__not_in={TITLED},{BODIED} --format=count` + Then STDOUT should be: + """ + 1 + """ + + # Unlike --name, this reaches a draft, because it is not a single-post query. + When I run `wp post list --post_name__in=plain --field=post_title` + Then STDOUT should be: + """ + Plain + """ + + When I run `wp post list --s=zebra --format=count` + Then STDOUT should be: + """ + 2 + """ + + # The nested queries are given as JSON, which this command decodes. + When I run `wp post list --meta_query='[{"key":"rank","value":"5"}]' --field=post_title` + Then STDOUT should be: + """ + Zebra Title + """ + + When I run `wp post list --meta_key=rank --meta_value=4 --meta_compare=">" --field=post_title` + Then STDOUT should be: + """ + Zebra Title + """ + + # --offset only means anything once --posts_per_page is bounded. + When I run `wp post list --posts_per_page=10 --offset=1 --format=count` + Then STDOUT should be: + """ + 2 + """ + + When I run `wp post list --posts_per_page=1 --nopaging=1 --format=count` + Then STDOUT should be: + """ + 3 + """ + + # 'search_columns' is a WP_Query argument as of WordPress 6.2. Before that it + # is not recognised, so '--s' searches every column and the narrowing here + # would not happen. + @require-wp-6.2 + Scenario: Narrowing a search to particular columns + When I run `wp post create --post_title='Zebra Title' --post_content='nothing' --post_status=publish --porcelain` + Then STDOUT should be a number + + When I run `wp post create --post_title='Plain' --post_content='zebra in body' --post_status=publish --porcelain` + Then STDOUT should be a number + + When I run `wp post list --s=zebra --format=count` + Then STDOUT should be: + """ + 2 + """ + + When I run `wp post list --s=zebra --search_columns=post_title --field=post_title` + Then STDOUT should be: + """ + Zebra Title + """ + + When I run `wp post list --s=zebra --search_columns=post_content --field=post_title` + Then STDOUT should be: + """ + Plain + """ + + When I run `wp post list --s=zebra --search_columns=post_title,post_content --format=count` + Then STDOUT should be: + """ + 2 + """ diff --git a/src/Post_Command.php b/src/Post_Command.php index ab3a26f79..1880f24b5 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -644,6 +644,17 @@ protected function delete_callback( $post_id, $assoc_args ) { * [--author_name=<author_name>] * : Filter by the 'user_nicename' of the post's author. * + * [--cat=<cat>] + * : Filter by category ID. Accepts a comma-separated list to match any of + * them, and a negative ID excludes that category instead. + * + * [--category_name=<category_name>] + * : Filter by category slug. + * + * [--tag=<tag>] + * : Filter by tag slug. Accepts a comma-separated list to match any of them, + * or a '+'-separated list to require all of them. + * * [--post_type=<post_type>] * : Filter by post type. Defaults to 'post'. Accepts a comma-separated list, * or 'any' for every type registered without 'exclude_from_search'. @@ -683,12 +694,137 @@ protected function delete_callback( $post_id, $assoc_args ) { * [--day=<day>] * : Filter by day of the month, 1 to 31. * + * [--hour=<hour>] + * : Filter by hour, 0 to 23. + * + * [--minute=<minute>] + * : Filter by minute, 0 to 59. + * + * [--second=<second>] + * : Filter by second, 0 to 59. + * * [--m=<yearmonth>] * : Filter by year and month together, e.g. 202401. * * [--w=<week>] * : Filter by week of the year, 0 to 53. * + * [--meta_key=<meta_key>] + * : Filter by posts having this meta key. Pair it with `--meta_value` to + * filter on the value as well. + * + * [--meta_value=<meta_value>] + * : Filter by this meta value. Needs `--meta_key` to say which key it + * belongs to. + * + * [--orderby=<orderby>] + * : Order the results by this field. Accepts what WP_Query accepts, e.g. + * 'date', 'title', 'ID', 'menu_order', 'rand', or 'meta_value' alongside + * `--meta_key`. + * + * [--order=<order>] + * : Direction to order by. Accepts 'ASC' or 'DESC'. + * + * [--post__in=<ids>] + * : Only list the posts with these IDs (comma-separated). + * + * [--post__not_in=<ids>] + * : Exclude the posts with these IDs (comma-separated). + * + * [--post_name__in=<slugs>] + * : Only list the posts with these slugs (comma-separated). Unlike `--name` + * this is not a single-post query, so it reaches drafts as well. + * + * [--post_parent__in=<ids>] + * : Only list the posts whose parent is one of these IDs (comma-separated). + * + * [--post_parent__not_in=<ids>] + * : Exclude the posts whose parent is one of these IDs (comma-separated). + * + * [--author__in=<ids>] + * : Only list the posts by these author IDs (comma-separated). + * + * [--author__not_in=<ids>] + * : Exclude the posts by these author IDs (comma-separated). + * + * [--category__in=<ids>] + * : Only list the posts in these category IDs (comma-separated). + * + * [--category__and=<ids>] + * : Only list the posts in all of these category IDs (comma-separated). + * + * [--category__not_in=<ids>] + * : Exclude the posts in these category IDs (comma-separated). + * + * [--tag_id=<tag_id>] + * : Filter by tag ID. + * + * [--tag__in=<ids>] + * : Only list the posts with these tag IDs (comma-separated). + * + * [--tag__and=<ids>] + * : Only list the posts with all of these tag IDs (comma-separated). + * + * [--tag__not_in=<ids>] + * : Exclude the posts with these tag IDs (comma-separated). + * + * [--tag_slug__in=<slugs>] + * : Only list the posts with these tag slugs (comma-separated). + * + * [--tag_slug__and=<slugs>] + * : Only list the posts with all of these tag slugs (comma-separated). + * + * [--page_id=<page_id>] + * : Filter by page ID. Needs `--post_type=page` to match anything. + * + * [--pagename=<pagename>] + * : Filter by page slug. Needs `--post_type=page` to match anything. + * + * [--attachment_id=<attachment_id>] + * : Filter by attachment ID. Needs `--post_type=attachment` to match + * anything. + * + * [--date_query=<json>] + * : Filter by a date query, given as JSON. See WP_Date_Query. + * + * [--meta_query=<json>] + * : Filter by a meta query, given as JSON. See WP_Meta_Query. + * + * [--tax_query=<json>] + * : Filter by a taxonomy query, given as JSON. See WP_Tax_Query. + * + * [--meta_compare=<meta_compare>] + * : Operator to test `--meta_value` with, e.g. '=', '!=', '>' or 'LIKE'. + * + * [--sentence=<sentence>] + * : Match `--s` as one phrase rather than as separate words. Accepts 1 or 0. + * + * [--exact=<exact>] + * : Match `--s` against the whole column rather than part of it. Accepts 1 + * or 0. + * + * [--search_columns=<columns>] + * : Comma-separated list of columns `--s` looks in. Accepts 'post_title', + * 'post_excerpt' and 'post_content'. Needs WordPress 6.2 or later. + * + * [--perm=<perm>] + * : Filter by what the current user may do with the post. Accepts + * 'readable' or 'editable'; pair it with the global `--user` argument, + * since WP-CLI is no user by default. + * + * [--posts_per_page=<number>] + * : How many posts to return. Defaults to -1, meaning every post. + * + * [--paged=<paged>] + * : Which page of results to return, counted in `--posts_per_page` steps. + * + * [--offset=<offset>] + * : How many posts to skip. Needs `--posts_per_page` set to something other + * than -1, which otherwise takes precedence. + * + * [--nopaging=<nopaging>] + * : Return every post, ignoring `--posts_per_page`. Accepts 1 or 0. + * * [--field=<field>] * : Prints the value of a single field for each post. *