JPKCom ACF References

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
author

Jean Pierre Kolb jpk@jpkc.com

license

GPL-2.0-or-later

link
https://github.com/JPKCom/jpkcom-acf-references

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
since
1.2.0

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
since
1.2.0

JPKCOM_ACFREFERENCES_ABILITY_MAX_VALUES

Largest number of values accepted in one filter axis.

public mixed JPKCOM_ACFREFERENCES_ABILITY_MAX_VALUES = 20
Tags
since
1.2.0

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
since
1.2.0

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
since
1.2.0

JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX

Largest page size the query ability will honour.

public mixed JPKCOM_ACFREFERENCES_ABILITY_PER_PAGE_MAX = 50
Tags
since
1.2.0

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
since
1.2.0

JPKCOM_ACFREFERENCES_ABILITY_VOCABULARY_LIMIT

Largest number of entries list-filters reports per axis.

public mixed JPKCOM_ACFREFERENCES_ABILITY_VOCABULARY_LIMIT = 500
Tags
since
1.2.0

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
since
1.2.0
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
since
1.2.0

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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
Return values
array<string|int, int>

IDs, deduplicated and capped.

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
since
1.2.0
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
since
1.2.0
Return values
array{terms: array>, truncated: bool}

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.

since
1.2.0
Return values
array{items: array>, truncated: bool}

Vocabulary.

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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0
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
since
1.2.0

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.

Tags
since
1.2.0
On this page

Search results