abilities.php
WordPress Abilities API integration.
Registers three read-only abilities so MCP clients, REST automation and the WordPress AI client can read this site's references as structured data instead of scraping the rendered page.
Everything here reads through includes/references-data.php. The visibility rule is not restated in these callbacks, because a second statement of a rule becomes a second rule the moment either side is touched.
That applies WITHIN this file only. references-data.php is not yet the single home of the rule: its builder is called from here and nowhere else, while includes/shortcodes.php still assembles the same clauses inline. The two were verified to agree, but they are two implementations — a change to the rule has to be made in both until the shortcode is moved over.
Tags
Table of Contents
Constants
- JPKCOM_ACFREFERENCES_ABILITY_CATEGORY : mixed = 'jpkcom-content'
- Ability category shared with the sibling JPKCom content plugins.
- JPKCOM_ACFREFERENCES_ABILITY_INPUT_KEYS : mixed = ['jpkcom-acf-references/list-filters' => [], 'j...
- Top-level input keys each ability declares.
- JPKCOM_ACFREFERENCES_ABILITY_MAX_VALUES : mixed = 20
- Largest number of values accepted in one filter axis.
- JPKCOM_ACFREFERENCES_ABILITY_PAGE_MAX : mixed = \intdiv(num1: \PHP_INT_MAX, num2: \max(1, \JPKC...
- Highest page number the query ability will ask the database for.
- JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_DEFAULT : mixed = 10
- Default page size for the query ability.
- JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX : mixed = 50
- Largest page size the query ability will honour.
- JPKCOM_ACFREFERENCES_ABILITY_SEARCH_MAX_BYTES : mixed = 1600
- Longest search term, in BYTES, that WordPress will actually apply.
- JPKCOM_ACFREFERENCES_ABILITY_VOCABULARY_LIMIT : mixed = 500
- Largest number of entries list-filters reports per axis.
Functions
- jpkcom_acf_references_abilities_enabled() : bool
- Decide whether the abilities should be registered at all.
- jpkcom_acf_references_ability_log() : void
- Write a debug line, and only with WP_DEBUG.
- jpkcom_acf_references_ability_error() : WP_Error
- Build a WP_Error carrying an HTTP status.
- jpkcom_acf_references_ability_boundary() : array<string, mixed>|WP_Error
- Turn a Throwable out of a callback into a WP_Error.
- jpkcom_acf_references_ability_json_object() : array<string, mixed>|stdClass
- Wrap an empty map so it encodes as {} rather than [].
- jpkcom_acf_references_ability_capability() : bool
- Capability required to run an ability.
- jpkcom_acf_references_ability_meta() : array<string, mixed>
- Build the meta array for an ability.
- jpkcom_acf_references_ability_validate_input_keys() : true|WP_Error
- Refuse a top-level input key the ability does not declare.
- jpkcom_acf_references_ability_normalise_input() : array<string, mixed>|null
- Bring the value core hands the callback into array form.
- jpkcom_acf_references_ability_slug_list() : array<string|int, string>
- Normalise a filter axis into a bounded list of term slugs.
- jpkcom_acf_references_ability_id_list() : array<string|int, int>
- Normalise a filter axis into a bounded list of positive post IDs.
- jpkcom_acf_references_ability_validate_search() : true|WP_Error
- Refuse a search term core would silently discard.
-
jpkcom_acf_references_ability_vocabulary()
: array{terms: array
>, truncated: bool} - Read one taxonomy's terms as the filter vocabulary.
-
jpkcom_acf_references_ability_related_vocabulary()
: array{items: array
>, truncated: bool} - Read the customers or locations a caller may filter by.
- jpkcom_acf_references_ability_count_query() : int
- Count posts matching a set of query arguments, cheaply.
- jpkcom_acf_references_ability_visibility_counts() : array<string, int>
- Report how many published references the listing rule excludes, and why.
- jpkcom_acf_references_ability_archive_url() : string
- The reference archive URL, or an empty string when there is none.
- jpkcom_acf_references_ability_permission_list_filters() : bool
- Permission callback for list-filters.
- jpkcom_acf_references_ability_permission_query_references() : bool
- Permission callback for query-references.
- jpkcom_acf_references_ability_permission_get_reference() : bool
- Permission callback for get-reference.
- jpkcom_acf_references_ability_list_filters_inner() : array<string, mixed>|WP_Error
- Report the values a caller may filter references by.
- jpkcom_acf_references_ability_query_references_inner() : array<string, mixed>|WP_Error
- Run a filtered, paginated query over publicly listed references.
- jpkcom_acf_references_ability_get_reference_inner() : array<string, mixed>|WP_Error
- Return one reference by ID.
- jpkcom_acf_references_ability_list_filters() : array<string, mixed>|WP_Error
- Execute callback for jpkcom-acf-references/list-filters.
- jpkcom_acf_references_ability_query_references() : array<string, mixed>|WP_Error
- Execute callback for jpkcom-acf-references/query-references.
- jpkcom_acf_references_ability_get_reference() : array<string, mixed>|WP_Error
- Execute callback for jpkcom-acf-references/get-reference.
- jpkcom_acf_references_get_ability_definitions() : array<string, array<string, mixed>>
- Build the registration arguments for every ability this plugin provides.
- jpkcom_acf_references_register_ability_category() : void
- Register the shared category, unless a sibling plugin already did.
- jpkcom_acf_references_register_abilities() : void
- Register every ability this plugin provides.
Constants
JPKCOM_ACFREFERENCES_ABILITY_CATEGORY
Ability category shared with the sibling JPKCom content plugins.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_CATEGORY
= 'jpkcom-content'
Categories are global and registration is FIRST-WINS: the second plugin to register the same slug gets null back and _doing_it_wrong() fires. Which plugin wins depends on load order, so registration goes through wp_has_ability_category() rather than assuming.
Tags
JPKCOM_ACFREFERENCES_ABILITY_INPUT_KEYS
Top-level input keys each ability declares.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_INPUT_KEYS
= ['jpkcom-acf-references/list-filters' => [], 'jpkcom-acf-references/query-references' => ['type', 'filter_1', 'filter_2', 'customer', 'location', 'search', 'page', 'per_page', 'order'], 'jpkcom-acf-references/get-reference' => ['id']]
One list, one place, and checked against the registered input schemas by
tests/test-abilities.php. Neither schema that carries properties declares
additionalProperties, on purpose: core's validate_input() runs BEFORE the
execute callback, so declaring it would preempt the guard below and replace
a message naming the accepted keys with core's "not a valid property of the
object". Self-correction in one turn is the point of these messages.
list-filters is the exception and must stay one: it declares no properties at all, so additionalProperties => false is the only thing that can refuse a key there.
Tags
JPKCOM_ACFREFERENCES_ABILITY_MAX_VALUES
Largest number of values accepted in one filter axis.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_MAX_VALUES
= 20
Tags
JPKCOM_ACFREFERENCES_ABILITY_PAGE_MAX
Highest page number the query ability will ask the database for.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_PAGE_MAX
= \intdiv(num1: \PHP_INT_MAX, num2: \max(1, \JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX))
Derived, not picked. WP_Query computes its LIMIT offset as absint( ( $page - 1 ) * $posts_per_page ), a plain integer multiplication: past PHP_INT_MAX it becomes a float and absint() casts rather than throws, collapsing the offset to 0 — so page one's records come back labelled as a page far beyond total_pages.
max() guards the divisor: a site may redefine PER_PAGE_MAX, and intdiv() by zero is a fatal at file load.
Tags
JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_DEFAULT
Default page size for the query ability.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_DEFAULT
= 10
NOT the shortcode's default. That one is -1, which means "all" there and would mean an unbounded response here.
Tags
JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX
Largest page size the query ability will honour.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX
= 50
Tags
JPKCOM_ACFREFERENCES_ABILITY_SEARCH_MAX_BYTES
Longest search term, in BYTES, that WordPress will actually apply.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_SEARCH_MAX_BYTES
= 1600
WP_Query::parse_query() empties s when strlen() exceeds this — an anti-DoS
guard that runs INSIDE the query, after every check on the arguments has
passed. The result is not an error: the search simply stops narrowing and
every reference matches, while the response still echoes the term back as
applied.
strlen(), so the unit is BYTES. Counting characters would hand a 900- character accented term to a guard that counts bytes and reproduce the defect for non-ASCII callers only.
Tags
JPKCOM_ACFREFERENCES_ABILITY_VOCABULARY_LIMIT
Largest number of entries list-filters reports per axis.
public
mixed
JPKCOM_ACFREFERENCES_ABILITY_VOCABULARY_LIMIT
= 500
Tags
Functions
jpkcom_acf_references_abilities_enabled()
Decide whether the abilities should be registered at all.
jpkcom_acf_references_abilities_enabled() : bool
Requires Plugins only blocks activation; core does not stop a dependency
being deactivated while dependents are active. get_field() is therefore
checked rather than assumed.
Tags
Return values
bool —True when registration should proceed.
jpkcom_acf_references_ability_log()
Write a debug line, and only with WP_DEBUG.
jpkcom_acf_references_ability_log(string $message) : void
Parameters
- $message : string
-
Message.
Tags
jpkcom_acf_references_ability_error()
Build a WP_Error carrying an HTTP status.
jpkcom_acf_references_ability_error(string $code, string $message[, int $status = 400 ]) : WP_Error
The status is not decoration. The REST run controller returns the WP_Error verbatim and rest_ensure_response() defaults to 500 without data['status'] — and a 5xx tells an agent "transient fault, retry unchanged", which is the exact opposite of what a caller mistake needs to hear.
Parameters
- $code : string
-
Error code.
- $message : string
-
Human-readable message.
- $status : int = 400
-
HTTP status.
Tags
Return values
WP_Error —Error.
jpkcom_acf_references_ability_boundary()
Turn a Throwable out of a callback into a WP_Error.
jpkcom_acf_references_ability_boundary(callable $body, string $ability) : array<string, mixed>|WP_Error
ACF throws while READING corrupt meta, so no check on the shape beforehand catches it. This rim does not catch everything: schema validation and the permission check both run before it.
Parameters
- $body : callable
-
Callback returning the ability result.
- $ability : string
-
Ability name, for the log line.
Tags
Return values
array<string, mixed>|WP_Error —Result or error.
jpkcom_acf_references_ability_json_object()
Wrap an empty map so it encodes as {} rather than [].
jpkcom_acf_references_ability_json_object(array<string, mixed> $map) : array<string, mixed>|stdClass
PHP serialises an empty array as the JSON array [], and a client validating
against a schema declaring type: object rejects that. Only the empty case
is wrapped, so PHP callers keep array access where there is data.
Parameters
- $map : array<string, mixed>
-
Map.
Tags
Return values
array<string, mixed>|stdClass —Map, or an empty object.
jpkcom_acf_references_ability_capability()
Capability required to run an ability.
jpkcom_acf_references_ability_capability(string $ability) : bool
Defaults to read. Every query is hard-scoped to published references, so
this cannot expose drafts — but it is bulk machine-readable access, and a
site may want it narrower.
Parameters
- $ability : string
-
Ability name.
Tags
Return values
bool —True when the current user may run it.
jpkcom_acf_references_ability_meta()
Build the meta array for an ability.
jpkcom_acf_references_ability_meta(string $ability) : array<string, mixed>
Three independent switches: show_in_rest (core REST), public (WP 7.1; inert passthrough before that), and mcp.public — not a core key at all, but the MCP Adapter's own gate for discovery AND execution.
All three annotations are set explicitly. They default to null, and the REST run controller derives the HTTP verb from them: readonly makes the run route GET-only and POST answers 405.
Parameters
- $ability : string
-
Ability name.
Tags
Return values
array<string, mixed> —Meta array.
jpkcom_acf_references_ability_validate_input_keys()
Refuse a top-level input key the ability does not declare.
jpkcom_acf_references_ability_validate_input_keys(array<string, mixed> $input, array<string|int, string> $allowed) : true|WP_Error
On EVERY ability, not a subset. A guard on some of them is a trap: a caller that learned the refusal on one assumes it everywhere, and the ability that silently accepts is the one it will trust. The sibling plugin shipped exactly that gap for a whole release.
Without this, an axis the ability does not declare is not applied and the answer is the complete unfiltered set behind an HTTP 200 — a caller has to notice an absence to notice the failure.
Parameters
- $input : array<string, mixed>
-
Raw ability input.
- $allowed : array<string|int, string>
-
Declared keys.
Tags
Return values
true|WP_Error —True when every key is declared.
jpkcom_acf_references_ability_normalise_input()
Bring the value core hands the callback into array form.
jpkcom_acf_references_ability_normalise_input(mixed $input) : array<string, mixed>|null
normalize_input() substitutes the schema's TOP-LEVEL default when the input is exactly null, and that default is a stdClass — so the callback receives an object and must read it. A callback that only accepts an array answers 400 to the most obvious call it has. This exact defect shipped twice in the sibling plugins.
Parameters
- $input : mixed
-
Raw input.
Tags
Return values
array<string, mixed>|null —Array form, or null when unusable.
jpkcom_acf_references_ability_slug_list()
Normalise a filter axis into a bounded list of term slugs.
jpkcom_acf_references_ability_slug_list(mixed $value) : array<string|int, string>
Parameters
- $value : mixed
-
Raw value.
Tags
Return values
array<string|int, string> —Slugs, deduplicated and capped.
jpkcom_acf_references_ability_id_list()
Normalise a filter axis into a bounded list of positive post IDs.
jpkcom_acf_references_ability_id_list(mixed $value) : array<string|int, int>
Parameters
- $value : mixed
-
Raw value.
Tags
Return values
array<string|int, int> —IDs, deduplicated and capped.
jpkcom_acf_references_ability_validate_search()
Refuse a search term core would silently discard.
jpkcom_acf_references_ability_validate_search(string $search) : true|WP_Error
Parameters
- $search : string
-
Search term.
Tags
Return values
true|WP_Error —True when the term is usable.
jpkcom_acf_references_ability_vocabulary()
Read one taxonomy's terms as the filter vocabulary.
jpkcom_acf_references_ability_vocabulary(string $taxonomy) : array{terms: array>, truncated: bool}
hide_empty is false: a term with no reference today is still a valid filter value, and reporting only used terms would make the vocabulary shift under a caller that cached it.
Parameters
- $taxonomy : string
-
Taxonomy slug.
Tags
Return values
array{terms: arrayVocabulary.
jpkcom_acf_references_ability_related_vocabulary()
Read the customers or locations a caller may filter by.
jpkcom_acf_references_ability_related_vocabulary(string $post_type) : array{items: array>, truncated: bool}
Published only, and projected to id/title. Never a WP_Post: ACF resolves these relations with post_status => 'any' elsewhere, and WP_Post exposes post_password as a public property.
Parameters
- $post_type : string
-
Post type.
Tags
Return values
array{items: arrayVocabulary.
jpkcom_acf_references_ability_count_query()
Count posts matching a set of query arguments, cheaply.
jpkcom_acf_references_ability_count_query(array<string, mixed> $args) : int
Parameters
- $args : array<string, mixed>
-
Query arguments.
Tags
Return values
int —Count.
jpkcom_acf_references_ability_visibility_counts()
Report how many published references the listing rule excludes, and why.
jpkcom_acf_references_ability_visibility_counts() : array<string, int>
Both numbers are DIFFERENCES, derived from the rule itself. Restating what "expired" means as its own meta_query is the defect this shape exists to prevent: the rule's expiry clause is an OR group of three branches, and negating only the first is not its complement — MariaDB casts '' to '0000-00-00', so every reference whose date had been saved and cleared would be counted as expired in the same response that lists it.
hidden_expired = ( published + featured row ) - listed hidden_missing_featured = published - ( published + featured row )
Tags
Return values
array<string, int> —Counts.
jpkcom_acf_references_ability_archive_url()
The reference archive URL, or an empty string when there is none.
jpkcom_acf_references_ability_archive_url() : string
Tags
Return values
string —Archive URL.
jpkcom_acf_references_ability_permission_list_filters()
Permission callback for list-filters.
jpkcom_acf_references_ability_permission_list_filters([mixed $input = null ]) : bool
Parameters
- $input : mixed = null
-
Validated input, unused.
Tags
Return values
bool —True when the current user may run it.
jpkcom_acf_references_ability_permission_query_references()
Permission callback for query-references.
jpkcom_acf_references_ability_permission_query_references([mixed $input = null ]) : bool
Parameters
- $input : mixed = null
-
Validated input, unused.
Tags
Return values
bool —True when the current user may run it.
jpkcom_acf_references_ability_permission_get_reference()
Permission callback for get-reference.
jpkcom_acf_references_ability_permission_get_reference([mixed $input = null ]) : bool
Parameters
- $input : mixed = null
-
Validated input, unused.
Tags
Return values
bool —True when the current user may run it.
jpkcom_acf_references_ability_list_filters_inner()
Report the values a caller may filter references by.
jpkcom_acf_references_ability_list_filters_inner([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_ability_query_references_inner()
Run a filtered, paginated query over publicly listed references.
jpkcom_acf_references_ability_query_references_inner([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_ability_get_reference_inner()
Return one reference by ID.
jpkcom_acf_references_ability_get_reference_inner([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_ability_list_filters()
Execute callback for jpkcom-acf-references/list-filters.
jpkcom_acf_references_ability_list_filters([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_ability_query_references()
Execute callback for jpkcom-acf-references/query-references.
jpkcom_acf_references_ability_query_references([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_ability_get_reference()
Execute callback for jpkcom-acf-references/get-reference.
jpkcom_acf_references_ability_get_reference([mixed $input = null ]) : array<string, mixed>|WP_Error
Parameters
- $input : mixed = null
-
Ability input.
Tags
Return values
array<string, mixed>|WP_Error —Result.
jpkcom_acf_references_get_ability_definitions()
Build the registration arguments for every ability this plugin provides.
jpkcom_acf_references_get_ability_definitions() : array<string, array<string, mixed>>
Reads no WordPress state and touches no registry, which is what lets the CI harness assert the shape of these arrays without a WordPress installation. Not free of side effects though: __() and the meta filter each fire apply_filters(), so third-party callbacks run whenever this is called.
Tags
Return values
array<string, array<string, mixed>> —Ability name => registration args.
jpkcom_acf_references_register_ability_category()
Register the shared category, unless a sibling plugin already did.
jpkcom_acf_references_register_ability_category() : void
Categories are global and first-wins. Without the check all abilities still register - the category exists either way - but _doing_it_wrong() fires, and which plugin wins depends on load order.
Tags
jpkcom_acf_references_register_abilities()
Register every ability this plugin provides.
jpkcom_acf_references_register_abilities() : void
wp_register_ability() returns null on EVERY failure path and reports only through _doing_it_wrong(), which is silent in production - and so is the debug log without WP_DEBUG. The return value is checked, but do not expect a registration failure to announce itself on a customer site.