JPKCom Harden & Trim

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

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

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
since
1.1.0
see

jpkcom-disable-xmlrpc/jpkcom-disable-xmlrpc.php jpkcom_disable_xmlrpc_is_xmlrpc_request()

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
since
1.1.0
see

jpkcom-disable-xmlrpc/jpkcom-disable-xmlrpc.php jpkcom_disable_xmlrpc_fault_xml()

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
since
1.1.0
see

jpkcom-disable-xmlrpc/jpkcom-disable-xmlrpc.php the init priority 0 closure

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
since
1.1.0
see

jpkcom-disable-xmlrpc/jpkcom-disable-xmlrpc.php the wp_xmlrpc_server_class closure

Return values
never

jpkcom_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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php jpkcom_disable_comments_close_rest_status()

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 type parameter is exactly note - 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 type note (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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php jpkcom_disable_comments_is_note_request()

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php jpkcom_disable_comments_is_note_query()

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the rest_api_init closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the wp_loaded closure

jpkcom_harden_trim_comments_redirect_screen()

`admin_init` callback: redirect the comments screen to the dashboard.

jpkcom_harden_trim_comments_redirect_screen() : void
Tags
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the admin_init closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the wp_count_comments closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the comments_pre_query closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the template_redirect closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the admin_menu closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the admin_bar_menu closure

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
since
1.1.0
see

jpkcom-disable-comments/jpkcom-disable-comments.php the rest_pre_dispatch closure

Return values
mixed —

The incoming value, or the 404 response.

On this page

Search results