features-sitewide.php
Site-wide features
Two switches that mirror standalone JPKCom plugins hook for hook:
xmlrpc (JPKCom Disable XML-RPC 1.0.10) and comments (JPKCom Disable
Comments 1.0.11). Same hooks, priorities, accepted args and return values;
only the callback names and the text domain differ, plus two documented
exceptions: the XML-RPC fault text is an English literal, and the comment
feed refusal stays a wp_die() 404 instead of going through
jpkcom_harden_trim_force_404(). Both are global (no role exemption). The
standalone plugins' findings up to their 1.0.10 were measured against
WordPress 7.0.2; the Notes gate, the note-query pass-through and the comment
count filter (Disable Comments 1.0.11) were read in the 7.1.3 source and not
yet measured on a running site. The line references below are 7.1.3.
Tags
Table of Contents
Functions
- jpkcom_harden_trim_register_sitewide_features() : void
- Register the hooks of the active site-wide features.
- jpkcom_harden_trim_is_xmlrpc_request() : bool
- Whether the current request is being served by xmlrpc.php.
- jpkcom_harden_trim_xmlrpc_fault_xml() : string
- Build the XML-RPC fault sent in place of a served request.
- jpkcom_harden_trim_xmlrpc_refuse() : void
- `init` priority 0: reject XML-RPC requests with a 403 and an XML-RPC fault.
- jpkcom_harden_trim_xmlrpc_server_class() : never
- `wp_xmlrpc_server_class` callback, last resort: block the server class.
- jpkcom_harden_trim_comments_close_rest_status() : mixed
- Report comments and pings as closed in a REST post response.
- jpkcom_harden_trim_comments_is_note_request() : bool
- Whether a REST request on the comment routes is a block-editor Notes request.
- jpkcom_harden_trim_comments_is_note_query() : bool
- Whether a comment query asks for notes and nothing else.
- jpkcom_harden_trim_comments_attach_rest_status() : void
- `rest_api_init` callback: apply the REST status rewrite to every post type.
- jpkcom_harden_trim_comments_remove_support() : void
- `wp_loaded` callback: remove comment and trackback support from every post type.
- jpkcom_harden_trim_comments_redirect_screen() : void
- `admin_init` callback: redirect the comments screen to the dashboard.
- jpkcom_harden_trim_comments_zero_counts() : object
- `wp_count_comments` callback: report zero comments everywhere.
- jpkcom_harden_trim_comments_short_circuit_query() : mixed
- `comments_pre_query` callback: short-circuit every comment query except note-only ones.
- jpkcom_harden_trim_comments_block_feed() : void
- `template_redirect` priority 0: refuse the comment feeds.
- jpkcom_harden_trim_comments_remove_menu() : void
- `admin_menu` callback: remove the Comments entry from the admin menu.
- jpkcom_harden_trim_comments_remove_admin_bar_node() : void
- `admin_bar_menu` priority 999: remove the Comments node from the admin bar.
- jpkcom_harden_trim_comments_rest_gate() : mixed
- `rest_pre_dispatch` callback: close the REST comment routes to everything except block-editor Notes.
Functions
jpkcom_harden_trim_register_sitewide_features()
Register the hooks of the active site-wide features.
jpkcom_harden_trim_register_sitewide_features(array<string, mixed> $settings) : void
Nothing is registered for a switch that is off. Registered at plugin
load (plugins_loaded priority 5), so the XML-RPC guard is in place
before init.
Parameters
- $settings : array<string, mixed>
-
Effective settings.
Tags
jpkcom_harden_trim_is_xmlrpc_request()
Whether the current request is being served by xmlrpc.php.
jpkcom_harden_trim_is_xmlrpc_request() : bool
XMLRPC_REQUEST is defined at the top of xmlrpc.php (7.1.3
xmlrpc.php:13), before wp-load.php runs, so it is set for every
request that reaches the endpoint and cannot be spoofed by the request.
The standalone plugin derived it from $_SERVER['SCRIPT_FILENAME'] up to
1.0.8, which depends on how a SAPI fills that variable.
Tags
Return values
bool —True when serving an XML-RPC request.
jpkcom_harden_trim_xmlrpc_fault_xml()
Build the XML-RPC fault sent in place of a served request.
jpkcom_harden_trim_xmlrpc_fault_xml() : string
A fault response keeps the refusal machine-readable for XML-RPC clients,
which cannot parse an HTML error page. The fault string is the English
literal, not a translation: the guard runs at init priority 0, before
this plugin loads its text domain at init priority 1, and the body is
read by machines. It is escaped like the standalone plugin's string.
Tags
Return values
string —A complete XML-RPC methodResponse fault document (faultCode 403).
jpkcom_harden_trim_xmlrpc_refuse()
`init` priority 0: reject XML-RPC requests with a 403 and an XML-RPC fault.
jpkcom_harden_trim_xmlrpc_refuse() : void
The response is written here instead of being left to wp_die(). With
XMLRPC_REQUEST defined, wp_die() dispatches to
_xmlrpc_wp_die_handler(), which only emits anything when the global
$wp_xmlrpc_server already exists (7.1.3 functions.php:4254), and
xmlrpc.php creates that object after init (xmlrpc.php:82). That
handler also never calls status_header(). Measured by the standalone
plugin against 7.0.2: up to its 1.0.8 the endpoint answered HTTP 200 with
an empty body.
Priority 0 puts it ahead of the text domain (1) and the updater (5), so a refused request does no further work.
Tags
jpkcom_harden_trim_xmlrpc_server_class()
`wp_xmlrpc_server_class` callback, last resort: block the server class.
jpkcom_harden_trim_xmlrpc_server_class(string $class) : never
Unreachable while the init guard is in place (xmlrpc.php:81 runs after
init), and kept so that removing or short-circuiting the guard cannot
silently re-enable the endpoint. status_header() is called explicitly
because the XML-RPC die handler never sets one. These strings are
translated: the text domain is loaded by then.
Parameters
- $class : string
-
The XML-RPC server class name.
Tags
Return values
neverjpkcom_harden_trim_comments_close_rest_status()
Report comments and pings as closed in a REST post response.
jpkcom_harden_trim_comments_close_rest_status(mixed $response) : mixed
Removing post type support is not enough for post, page and
attachment: WP_REST_Posts_Controller::get_item_schema() carries a
hardcoded $fixed_schemas list for those three in which comments is
always present (7.1.3 class-wp-rest-posts-controller.php:2552, :2586-2588);
the post_type_supports() check applies only to other post types. The
response therefore kept reporting the stored value, typically open.
The fields are rewritten rather than removed, so the block editor keeps
the response shape it expects. A REST write can still store open, but
it is inert: comments_open is filtered false and this reports closed.
Parameters
- $response : mixed
-
Prepared response, a WP_REST_Response in practice.
Tags
Return values
mixed —The response with both status fields set to closed.
jpkcom_harden_trim_comments_is_note_request()
Whether a REST request on the comment routes is a block-editor Notes request.
jpkcom_harden_trim_comments_is_note_request(WP_REST_Request $request) : bool
Notes (core since 6.9) are comments of type note, created and read
through /wp/v2/comments. A request counts as a note request when
- it targets the collection route and its
typeparameter is exactlynote- the same strict comparison core's controller makes ('note' === $request['type']), for GET and POST alike; - it targets the item route
/wp/v2/comments/<id>and that comment exists and is of typenote(any method; core refuses to change a comment's type on update, 7.1.3 class-wp-rest-comments-controller.php:925).
The route is read from get_route() and matched the way core matches it
(class-wp-rest-server.php:1172): case-insensitively, against the same
[\d]+ item pattern, and with $ accepting a trailing newline as core's
@^…$@i does. A trailing slash is tolerated as well. The ID is taken from
the route because the URL parameters are not populated yet on
rest_pre_dispatch: dispatch() applies the filter (:1079) before it
matches the route (:1096), and a batch sub-request is passed with its URL
parameters cleared (:1860-1866).
The controller itself does not use the ID in the route: it reads
$request['id'] (class-wp-rest-comments-controller.php:435, :480, :873,
:918, :1042, :1066), and get_param() prefers the JSON body, the form body
and the query string to the URL parameters (class-wp-rest-request.php:362-384,
:408-418). /wp/v2/comments/<note>?id=<n> would therefore load comment
<n>. An item request with an id parameter is a note request only when
that parameter is numeric and equal to the ID in the route. Read here,
before routing, get_param( 'id' ) returns exactly the value core will
prefer later, since the URL parameters are still empty.
Core's own permission checks still decide who may read or write notes.
Parameters
- $request : WP_REST_Request
-
The request about to be dispatched.
Tags
Return values
bool —True for a note request, false for anything else.
jpkcom_harden_trim_comments_is_note_query()
Whether a comment query asks for notes and nothing else.
jpkcom_harden_trim_comments_is_note_query(WP_Comment_Query $query) : bool
Mirrors how WP_Comment_Query::get_comment_ids() reads the requested
types (7.1.3 class-wp-comment-query.php:776-789): type and type__in
are merged, and an empty string means "all types". The query counts as a
note query only when, after dropping those empty entries, exactly note
remains. all, comment, an empty type, a mix such as
[ 'note', 'comment' ] or a spelling core would not treat as a note
(NOTE, note,comment) do not qualify.
Parameters
- $query : WP_Comment_Query
-
The query about to run.
Tags
Return values
bool —True when only notes are requested.
jpkcom_harden_trim_comments_attach_rest_status()
`rest_api_init` callback: apply the REST status rewrite to every post type.
jpkcom_harden_trim_comments_attach_rest_status() : void
Registered on rest_api_init because the filter name is per post type and every post type is registered by then.
Tags
jpkcom_harden_trim_comments_remove_support()
`wp_loaded` callback: remove comment and trackback support from every post type.
jpkcom_harden_trim_comments_remove_support() : void
Bound to wp_loaded, not admin_init: every post type is registered
once init has completed (7.1.3 wp-settings.php:779 init, :801
wp_loaded), while admin_init and rest_api_init fire later. On
admin_init (the standalone plugin up to 1.0.8) the removal never
reached REST requests, so /wp/v2/posts kept reporting
comment_status: open.
The two supports are checked independently: a post type may declare
trackbacks without comments, and a nested check left those untouched.
Tags
jpkcom_harden_trim_comments_redirect_screen()
`admin_init` callback: redirect the comments screen to the dashboard.
jpkcom_harden_trim_comments_redirect_screen() : void
Tags
jpkcom_harden_trim_comments_zero_counts()
`wp_count_comments` callback: report zero comments everywhere.
jpkcom_harden_trim_comments_zero_counts(mixed $counts, mixed $post_id) : object
The dashboard's "At a Glance" widget builds its comment line from
wp_count_comments() (filter at 7.1.3 comment.php:1529); with a non-zero
count it linked to edit-comments.php, which is redirected away. A
non-empty object also keeps core from running its own count query.
Parameters
- $counts : mixed
-
Counts assembled so far, an empty array by default.
- $post_id : mixed
-
Post ID the counts were requested for.
Tags
Return values
object —Zeroed count object in the shape core expects.
jpkcom_harden_trim_comments_short_circuit_query()
`comments_pre_query` callback: short-circuit every comment query except note-only ones.
jpkcom_harden_trim_comments_short_circuit_query(mixed $comment_data, WP_Comment_Query $query) : mixed
comments_array only covers the template path. The dashboard Activity
widget queries comments straight through WP_Comment_Query (filter at
7.1.3 class-wp-comment-query.php:441), which is why the full moderation
list still appeared there. dashboard_recent_comments has not been a
widget since WordPress 3.8, so removing that meta box does nothing.
Queries for block-editor Notes and nothing else pass through untouched
(see jpkcom_harden_trim_comments_is_note_query()), so the Notes panel can
list notes and core gets the real note count. Notes are not public: core
leaves them out of every query that does not ask for them (:782-789), and
out of comment_count (comment.php:3138).
Parameters
- $comment_data : mixed
-
Short-circuit value, null by default.
- $query : WP_Comment_Query
-
The query instance.
Tags
Return values
mixed —0 for counting queries, an empty array otherwise; the
incoming value for a note-only query.
jpkcom_harden_trim_comments_block_feed()
`template_redirect` priority 0: refuse the comment feeds.
jpkcom_harden_trim_comments_block_feed() : void
Comment feeds are assembled by WP_Query directly through $wpdb (only
the comment_feed_* filters apply), so they bypass comments_array and
comments_pre_query: approved comments stayed readable at
/comments/feed/ and <post>/feed/. /feed/ is unaffected, because
is_comment_feed() is false there.
Deliberately a wp_die( ..., 404 ) and not
jpkcom_harden_trim_force_404(), as in the standalone plugin: while
is_comment_feed() is true core routes it to _xml_wp_die_handler
(7.1.3 functions.php:3866-3878), so the answer is a 404 XML error
document, not the theme's 404 template.
Tags
jpkcom_harden_trim_comments_remove_menu()
`admin_menu` callback: remove the Comments entry from the admin menu.
jpkcom_harden_trim_comments_remove_menu() : void
Tags
jpkcom_harden_trim_comments_remove_admin_bar_node()
`admin_bar_menu` priority 999: remove the Comments node from the admin bar.
jpkcom_harden_trim_comments_remove_admin_bar_node(WP_Admin_Bar $wp_admin_bar) : void
Parameters
- $wp_admin_bar : WP_Admin_Bar
-
The admin bar instance.
Tags
jpkcom_harden_trim_comments_rest_gate()
`rest_pre_dispatch` callback: close the REST comment routes to everything except block-editor Notes.
jpkcom_harden_trim_comments_rest_gate(mixed $result, mixed $server, mixed $request) : mixed
Up to Disable Comments 1.0.10 the /wp/v2/comments routes were removed on
rest_endpoints. That also broke Notes, which core reads and writes
through exactly those routes. The routes now stay registered, and this
gate answers every request on them that is not a note request (see
jpkcom_harden_trim_comments_is_note_request()) the way a missing route is
answered: rest_no_route, core's own message (default text domain, so the
translation is core's too) and status 404.
A request is on the comment routes when its route is /wp/v2/comments or
lies below /wp/v2/comments/, compared case-insensitively as core does;
?rest_route= requests arrive with the same route. /wp/v2/commentsfoo
and other namespaces are not touched.
PHP_INT_MAX, and a non-note request is answered even when an earlier filter already produced a result - that includes core's OPTIONS handler at priority 10 (7.1.3 rest-api.php:837), which would otherwise describe the route. For a note request the incoming value is returned unchanged.
The error is returned as a response, not as a WP_Error: in a batch
request core passes a rest_pre_dispatch result straight on to
rest_post_dispatch (class-wp-rest-server.php:1894), where
rest_send_allow_header() calls get_matched_route() on it, which a
WP_Error does not have. In a single request dispatch() returns the
response as is - the same bytes as core's own missing-route answer.
Side effect: the route index at /wp-json/ lists the comment routes
again; that is inherent to keeping them for Notes.
Parameters
- $result : mixed
-
Response from an earlier filter, null by default.
- $server : mixed
-
Server instance.
- $request : mixed
-
The request about to be dispatched.
Tags
Return values
mixed —The incoming value, or the 404 response.