PluginProbe ʕ •ᴥ•ʔ
Disable Comments – Remove Comments & Stop Spam [Multi-Site Support] / 2.6.2
Disable Comments – Remove Comments & Stop Spam [Multi-Site Support] v2.6.2
trunk 1.1.1 1.10.0 1.10.3 1.11.0 1.5 1.5.1 1.5.2 1.6 1.7 1.7.1 1.8.0 1.9.0 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.7.0
disable-comments / disable-comments.php
disable-comments Last commit date
assets 6 months ago includes 1 year ago languages 4 months ago views 6 months ago disable-comments.php 4 months ago readme.txt 4 months ago uninstall.php 6 years ago
disable-comments.php
1905 lines
1 <?php
2
3 /**
4 * Plugin Name: Disable Comments
5 * Plugin URI: https://wordpress.org/plugins/disable-comments/
6 * Description: Allows administrators to globally disable comments on their site. Comments can be disabled according to post type. You could bulk delete comments using Tools.
7 * Version: 2.6.2
8 * Author: WPDeveloper
9 * Author URI: https://wpdeveloper.com
10 * License: GPL-3.0+
11 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
12 * Text Domain: disable-comments
13 * Domain Path: /languages/
14 *
15 * @package Disable_Comments
16 */
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 class Disable_Comments {
23 const DB_VERSION = 8;
24 private static $instance = null;
25 private $options;
26 public $networkactive;
27 public $tracker;
28 public $is_CLI;
29 public $sitewide_settings;
30 public $setup_notice_flag;
31 private $modified_types = array();
32
33 public static function get_instance() {
34 if (is_null(self::$instance)) {
35 self::$instance = new self;
36 }
37 return self::$instance;
38 }
39
40 function __construct() {
41 define('DC_VERSION', '2.6.2');
42 define('DC_PLUGIN_SLUG', 'disable_comments_settings');
43 define('DC_PLUGIN_ROOT_PATH', dirname(__FILE__));
44 define('DC_PLUGIN_VIEWS_PATH', DC_PLUGIN_ROOT_PATH . '/views/');
45 define('DC_PLUGIN_ROOT_URI', plugins_url("/", __FILE__));
46 define('DC_ASSETS_URI', DC_PLUGIN_ROOT_URI . 'assets/');
47
48 // save settings
49 add_action('wp_ajax_disable_comments_save_settings', array($this, 'disable_comments_settings'));
50 add_action('wp_ajax_disable_comments_delete_comments', array($this, 'delete_comments_settings'));
51 add_action('wp_ajax_get_sub_sites', array($this, 'get_sub_sites'));
52
53 // Including cli.php
54 if (defined('WP_CLI') && WP_CLI) {
55 add_action('init', array($this, 'enable_cli'), 9999);
56 }
57
58 // are we network activated?
59 $this->networkactive = (is_multisite() && array_key_exists(plugin_basename(__FILE__), (array) get_site_option('active_sitewide_plugins')));
60 $this->is_CLI = defined('WP_CLI') && WP_CLI;
61
62 $this->sitewide_settings = get_site_option('disable_comments_sitewide_settings', false);
63 // Load options.
64 if ($this->networkactive && ($this->is_network_admin() || $this->sitewide_settings !== '1')) {
65 $this->options = get_site_option('disable_comments_options', array());
66 $this->options['disabled_sites'] = $this->get_disabled_sites();
67
68 $blog_id = get_current_blog_id();
69 if (
70 !$this->is_network_admin() && (
71 empty($this->options['disabled_sites']) ||
72 // if site disabled
73 empty($this->options['disabled_sites']["site_$blog_id"])
74 )
75 ) {
76 $this->options = [
77 'remove_everywhere' => false,
78 'disabled_post_types' => array(),
79 'extra_post_types' => array(),
80 'disabled_sites' => array(),
81 'remove_xmlrpc_comments' => 0,
82 'remove_rest_API_comments' => 0,
83 'show_existing_comments' => false,
84 'allowed_comment_types' => array(),
85 'settings_saved' => true,
86 'db_version' => $this->options['db_version']
87 ];
88 }
89 } else {
90 $this->options = get_option('disable_comments_options', array());
91 $not_configured = empty($this->options) || empty($this->options['settings_saved']);
92
93 if (is_multisite() && $not_configured && $this->sitewide_settings == '1') {
94 $this->options = get_site_option('disable_comments_options', array());
95 $this->options['is_network_options'] = true;
96 }
97 }
98
99
100 // If it looks like first run, check compat.
101 if (empty($this->options)) {
102 $this->check_compatibility();
103 }
104
105 $this->options['sitewide_settings'] = ($this->sitewide_settings == '1');
106
107 // Upgrade DB if necessary.
108 $this->check_db_upgrades();
109 $this->check_upgrades();
110
111 add_action('plugins_loaded', [$this, 'init_filters']);
112 add_action('wp_loaded', [$this, 'start_plugin_usage_tracking']);
113
114 // Add Site Health integration
115 add_filter('debug_information', array($this, 'add_site_health_info'));
116 }
117
118 public function is_network_admin() {
119 $sanitized_referer = isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : '';
120 if (is_network_admin() || !empty($sanitized_referer) && defined('DOING_AJAX') && DOING_AJAX && is_multisite() && preg_match('#^' . network_admin_url() . '#i', $sanitized_referer)) {
121 return true;
122 }
123 return false;
124 }
125 /**
126 * Enable CLI
127 * @since 2.0.0
128 */
129 public function enable_cli() {
130 require_once DC_PLUGIN_ROOT_PATH . "/includes/cli.php";
131 new Disable_Comment_Command($this);
132 }
133
134 public function admin_notice() {
135 if ($this->tracker instanceof DisableComments_Plugin_Tracker) {
136 if (isset($this->setup_notice_flag) && $this->setup_notice_flag === true) {
137 return;
138 }
139 $current_screen = get_current_screen()->id;
140 $has_caps = $this->networkactive && is_network_admin() ? current_user_can('manage_network_plugins') : current_user_can('manage_options');
141 // if( ! in_array( $current_screen, ['settings_page_disable_comments_settings', 'settings_page_disable_comments_settings-network']) && $has_caps ) {
142 if ($has_caps && in_array($current_screen, ['dashboard-network', 'dashboard'])) {
143 $this->tracker->notice();
144 }
145 }
146 }
147
148 public function start_plugin_usage_tracking() {
149 if ($this->networkactive && !$this->options['sitewide_settings']) {
150 $this->tracker = null;
151 return;
152 }
153 if (!class_exists('DisableComments_Plugin_Tracker')) {
154 include_once(DC_PLUGIN_ROOT_PATH . '/includes/class-plugin-usage-tracker.php');
155 }
156 $tracker = $this->tracker = DisableComments_Plugin_Tracker::get_instance(__FILE__, [
157 'opt_in' => true,
158 'goodbye_form' => true,
159 'item_id' => 'b0112c9030af6ba53de4'
160 ]);
161 $tracker->set_notice_options(array(
162 'notice' => __('Want to help make Disable Comments even better?', 'disable-comments'),
163 'extra_notice' => __('We collect non-sensitive diagnostic data and plugin usage information. Your site URL, WordPress & PHP version, plugins & themes and email address to send you the discount coupon. This data lets us make sure this plugin always stays compatible with the most popular plugins and themes. No spam, I promise.', 'disable-comments'),
164 ));
165 $tracker->init();
166 }
167
168 private function check_compatibility() {
169 if (version_compare($GLOBALS['wp_version'], '4.7', '<')) {
170 require_once(ABSPATH . 'wp-admin/includes/plugin.php');
171 deactivate_plugins(__FILE__);
172
173 // @phpcs:ignore WordPress.Security.NonceVerification.Recommended
174 if (isset($_GET['action']) && ($_GET['action'] == 'activate' || $_GET['action'] == 'error_scrape')) {
175 // translators: %s: WordPress version no.
176 exit(sprintf(esc_html__('Disable Comments requires WordPress version %s or greater.', 'disable-comments'), '4.7'));
177 }
178 }
179 }
180
181 private function check_db_upgrades() {
182 $old_ver = isset($this->options['db_version']) ? $this->options['db_version'] : 0;
183 if ($old_ver < self::DB_VERSION) {
184 if ($this->networkactive) {
185 $this->options['is_network_admin'] = true;
186 }
187 if ($old_ver < 2) {
188 // upgrade options from version 0.2.1 or earlier to 0.3.
189 $this->options['disabled_post_types'] = get_option('disable_comments_post_types', array());
190 delete_option('disable_comments_post_types');
191 }
192 if ($old_ver < 5) {
193 // simple is beautiful - remove multiple settings in favour of one.
194 $this->options['remove_everywhere'] = isset($this->options['remove_admin_menu_comments']) ? $this->options['remove_admin_menu_comments'] : false;
195 foreach (array('remove_admin_menu_comments', 'remove_admin_bar_comments', 'remove_recent_comments', 'remove_discussion', 'remove_rc_widget') as $v) {
196 unset($this->options[$v]);
197 }
198 }
199 if ($old_ver < 7 && function_exists('get_sites')) {
200 $this->options['disabled_sites'] = [];
201 $dc_options = get_site_option('disable_comments_options', array());
202
203 foreach (get_sites(['number' => 0, 'fields' => 'ids']) as $blog_id) {
204 if (isset($dc_options['disabled_sites'])) {
205 $this->options['disabled_sites']["site_$blog_id"] = in_array($blog_id, $dc_options['disabled_sites']);
206 } else {
207 $this->options['disabled_sites']["site_$blog_id"] = true;
208 }
209 }
210 $this->options['disabled_sites'] = $this->get_disabled_sites();
211 }
212
213 if ($old_ver < 8) {
214 // Add new show_existing_comments option with default value false
215 // This maintains backward compatibility - existing behavior is preserved
216 $this->options['show_existing_comments'] = false;
217 }
218
219 foreach (array('remove_everywhere', 'extra_post_types', 'show_existing_comments') as $v) {
220 if (!isset($this->options[$v])) {
221 $this->options[$v] = false;
222 }
223 }
224
225 $this->options['db_version'] = self::DB_VERSION;
226 $this->update_options();
227 }
228 }
229
230 public function check_upgrades() {
231 $dc_version = get_option('disable_comment_version');
232 if (version_compare($dc_version, '2.3.1', '<')) {
233 if ($this->is_remove_everywhere()) {
234 update_option('show_avatars', true);
235 }
236 }
237 if (!$dc_version || $dc_version != DC_VERSION) {
238 update_option('disable_comment_version', DC_VERSION);
239 }
240 }
241
242 private function update_options() {
243 if ($this->networkactive && !empty($this->options['is_network_admin']) && $this->options['is_network_admin']) {
244 unset($this->options['is_network_admin']);
245 update_site_option('disable_comments_options', $this->options);
246 } else {
247 update_option('disable_comments_options', $this->options);
248 }
249 }
250
251 public function get_disabled_sites($default = false) {
252 $disabled_sites = ['all' => true];
253 foreach (get_sites(['number' => 0, 'fields' => 'ids']) as $blog_id) {
254 $disabled_sites["site_{$blog_id}"] = true;
255 }
256 if ($default) {
257 return $disabled_sites;
258 }
259
260 $this->options['disabled_sites'] = isset($this->options['disabled_sites']) ? $this->options['disabled_sites'] : [];
261 $this->options['disabled_sites'] = wp_parse_args($this->options['disabled_sites'], $disabled_sites);
262 $disabled_sites = $this->options['disabled_sites'];
263 unset($disabled_sites['all']);
264 if (in_array(false, $disabled_sites)) {
265 $this->options['disabled_sites']['all'] = false;
266 } else {
267 $this->options['disabled_sites']['all'] = true;
268 }
269 return $this->options['disabled_sites'];
270 }
271
272 // public function get_disabled_count(){
273 // $disabled_sites = isset($this->options['disabled_sites']) ? $this->options['disabled_sites'] : [];
274 // unset($disabled_sites['all']);
275 // return array_sum($disabled_sites);
276 // }
277
278 /**
279 * Get an array of disabled post type.
280 */
281 public function get_disabled_post_types() {
282 $types = $this->options['disabled_post_types'];
283 // Not all extra_post_types might be registered on this particular site.
284 if ($this->networkactive && !empty($this->options['extra_post_types'])) {
285 foreach ((array) $this->options['extra_post_types'] as $extra) {
286 if (post_type_exists($extra)) {
287 $types[] = $extra;
288 }
289 }
290 }
291 return $types;
292 }
293
294 /**
295 * Check whether comments have been disabled on a given post type.
296 */
297 private function is_exclude_by_role() {
298 if (!empty($this->options['enable_exclude_by_role']) && !empty($this->options['exclude_by_role'])) {
299 if (is_user_logged_in()) {
300 $user = wp_get_current_user();
301 $roles = (array) $user->roles;
302 $diff = array_intersect($this->options['exclude_by_role'], $roles);
303 if (count($diff) || (in_array("administrator", $this->options['exclude_by_role']) && is_super_admin())) {
304 return true;
305 }
306 } else if (in_array('logged-out-users', $this->options['exclude_by_role'])) {
307 return true;
308 }
309 }
310 return false;
311 }
312 private function is_remove_everywhere() {
313 if ($this->is_exclude_by_role()) {
314 return false;
315 }
316 if (isset($this->options['remove_everywhere'])) {
317 return $this->options['remove_everywhere'];
318 }
319 return false;
320 }
321
322 /**
323 * Check whether comments have been disabled on a given post type.
324 */
325 private function is_post_type_disabled($type) {
326 if ($this->is_exclude_by_role()) {
327 return false;
328 }
329 return $type && in_array($type, $this->get_disabled_post_types());
330 }
331
332 public function init_filters() {
333 // These need to happen now.
334 if ($this->is_remove_everywhere()) {
335 add_action('widgets_init', array($this, 'disable_rc_widget'));
336 add_filter('wp_headers', array($this, 'filter_wp_headers'));
337 add_action('template_redirect', array($this, 'filter_query'), 9); // before redirect_canonical.
338
339 // Admin bar filtering has to happen here since WP 3.6.
340 add_action('template_redirect', array($this, 'filter_admin_bar'));
341 add_action('admin_init', array($this, 'filter_admin_bar'));
342
343 // Disable Comments REST API Endpoint (but allow notes)
344 add_filter('rest_endpoints', array($this, 'filter_rest_endpoints'));
345 add_filter('rest_pre_dispatch', array($this, 'filter_rest_comment_dispatch'), 10, 3);
346 add_filter('rest_comment_query', array($this, 'filter_rest_comment_query'), 10, 2);
347 }
348
349 // remove create comment via xmlrpc
350 if (isset($this->options['remove_xmlrpc_comments']) && intval($this->options['remove_xmlrpc_comments']) === 1) {
351 add_filter('xmlrpc_methods', array($this, 'disable_xmlrc_comments'));
352 }
353 // rest API Comment Block (but allow notes)
354 if (isset($this->options['remove_rest_API_comments']) && intval($this->options['remove_rest_API_comments']) === 1) {
355 add_filter('rest_endpoints', array($this, 'filter_rest_endpoints'));
356 add_filter('rest_pre_insert_comment', array($this, 'disable_rest_API_comments'), 10, 2);
357 add_filter('rest_pre_dispatch', array($this, 'filter_rest_comment_dispatch'), 10, 3);
358 add_filter('rest_comment_query', array($this, 'filter_rest_comment_query'), 10, 2);
359 }
360
361 // These can happen later.
362 add_action('wp_loaded', array($this, 'init_wploaded_filters'));
363 // Disable "Latest comments" block in Gutenberg.
364 add_action('enqueue_block_editor_assets', array($this, 'filter_gutenberg_blocks'));
365 // settings page assets
366 add_action('admin_enqueue_scripts', array($this, 'settings_page_assets'));
367
368 if (!$this->networkactive || $this->options['sitewide_settings']) {
369 add_filter('comment_status_links', function ($status_links) {
370 $status_links['disable_comments'] = sprintf("<a href='" . $this->settings_page_url() . "'>%s</a>", __("Disable Comments", 'disable-comments'));
371 return $status_links;
372 });
373 }
374 }
375
376 public function init_wploaded_filters() {
377 $disabled_post_types = $this->get_disabled_post_types();
378 if (!empty($disabled_post_types) && !$this->is_exclude_by_role()) {
379 foreach ($disabled_post_types as $type) {
380 // we need to know what native support was for later.
381 if (post_type_supports($type, 'comments')) {
382 $this->modified_types[] = $type;
383 // Keep comments support if show_existing_comments is enabled
384 // or if there are allowed comment types that need to be displayed
385 if (empty($this->options['show_existing_comments']) && !$this->has_allowed_comment_types()) {
386 remove_post_type_support($type, 'comments');
387 }
388 remove_post_type_support($type, 'trackbacks');
389 }
390 }
391 } elseif (is_admin() && !$this->is_configured()) {
392 /**
393 * It is possible that $disabled_post_types is empty if other
394 * plugins have disabled comments. Hence we also check for
395 * remove_everywhere. If you still get a warning you probably
396 * shouldn't be using this plugin.
397 */
398 add_action('all_admin_notices', array($this, 'setup_notice'));
399 }
400
401 if ($this->is_remove_everywhere() || (!empty($disabled_post_types) && !$this->is_exclude_by_role())) {
402 add_filter('comments_array', array($this, 'filter_existing_comments'), 20, 2);
403 add_filter('comments_open', array($this, 'filter_comment_status'), 20, 2);
404 add_filter('pings_open', array($this, 'filter_comment_status'), 20, 2);
405 add_filter('get_comments_number', array($this, 'filter_comments_number'), 20, 2);
406 }
407
408 // Filters for the admin only.
409 if (is_admin()) {
410 add_action('all_admin_notices', array($this, 'admin_notice'));
411 if ($this->networkactive && is_network_admin()) {
412 add_action('network_admin_menu', array($this, 'settings_menu'));
413 add_action('network_admin_menu', array($this, 'tools_menu'));
414 add_filter('network_admin_plugin_action_links', array($this, 'plugin_actions_links'), 10, 2);
415 } elseif (!$this->networkactive || $this->options['sitewide_settings']) {
416 add_action('admin_menu', array($this, 'settings_menu'));
417 add_action('admin_menu', array($this, 'tools_menu'));
418 add_filter('plugin_action_links', array($this, 'plugin_actions_links'), 10, 2);
419 if (is_multisite()) { // We're on a multisite setup, but the plugin isn't network activated.
420 register_deactivation_hook(__FILE__, array($this, 'single_site_deactivate'));
421 }
422 }
423 add_action('admin_notices', array($this, 'discussion_notice'));
424 add_filter('plugin_row_meta', array($this, 'set_plugin_meta'), 10, 2);
425
426 if ($this->is_remove_everywhere()) {
427 add_action('admin_menu', array($this, 'filter_admin_menu'), 9999); // do this as late as possible.
428 add_action('admin_print_styles-index.php', array($this, 'admin_css'));
429 add_action('admin_print_styles-profile.php', array($this, 'admin_css'));
430 add_action('wp_dashboard_setup', array($this, 'filter_dashboard'));
431 add_filter('pre_option_default_pingback_flag', '__return_zero');
432 }
433 }
434 // Filters for front end only.
435 else {
436 add_action('template_redirect', array($this, 'check_comment_template'));
437
438 if ($this->is_remove_everywhere()) {
439 add_filter('feed_links_show_comments_feed', '__return_false');
440 }
441 }
442 }
443
444 // public function get_option( $key, $default = false ){
445 // return $this->networkactive ? get_site_option( $key, $default ) : get_option( $key, $default );
446 // }
447 // public function update_option( $option, $value ){
448 // return $this->networkactive ? update_site_option( $option, $value ) : update_option( $option, $value );
449 // }
450 // public function delete_option( $option ){
451 // return $this->networkactive ? delete_site_option( $option ) : delete_option( $option );
452 // }
453
454 /**
455 * Replace the theme's comment template with a blank one.
456 * To prevent this, define DISABLE_COMMENTS_REMOVE_COMMENTS_TEMPLATE
457 * and set it to True
458 */
459 public function check_comment_template() {
460 if (is_singular() && ($this->is_remove_everywhere() || $this->is_post_type_disabled(get_post_type()))) {
461 if (!defined('DISABLE_COMMENTS_REMOVE_COMMENTS_TEMPLATE') || DISABLE_COMMENTS_REMOVE_COMMENTS_TEMPLATE == true) {
462 // Kill the comments template unless:
463 // - show_existing_comments is enabled, OR
464 // - there are allowed comment types that need to be displayed
465 if (empty($this->options['show_existing_comments']) && !$this->has_allowed_comment_types()) {
466 add_filter('comments_template', array($this, 'dummy_comments_template'), 20);
467 }
468 }
469 // Remove comment-reply script for themes that include it indiscriminately.
470 wp_deregister_script('comment-reply');
471 // feed_links_extra inserts a comments RSS link.
472 remove_action('wp_head', 'feed_links_extra', 3);
473 }
474 }
475
476 public function dummy_comments_template() {
477 return dirname(__FILE__) . '/views/comments.php';
478 }
479
480 public function is_xmlrpc_rest() {
481 // remove create comment via xmlrpc
482 if (isset($this->options['remove_xmlrpc_comments']) && intval($this->options['remove_xmlrpc_comments']) === 1) {
483 return true;
484 }
485 // rest API Comment Block
486 if (isset($this->options['remove_rest_API_comments']) && intval($this->options['remove_rest_API_comments']) === 1) {
487 return true;
488 }
489 return false;
490 }
491
492 /**
493 * Remove the X-Pingback HTTP header
494 */
495 public function filter_wp_headers($headers) {
496 unset($headers['X-Pingback']);
497 return $headers;
498 }
499
500 /**
501 * remove method wp.newComment
502 */
503 public function disable_xmlrc_comments($methods) {
504 unset($methods['wp.newComment']);
505 return $methods;
506 }
507
508 public function disable_rest_API_comments($prepared_comment, $request) {
509 // Allow comment types in the allowlist (e.g., WordPress 6.9+ block notes)
510 if ($this->is_allowed_comment_type_request($request)) {
511 return $prepared_comment;
512 }
513 return;
514 }
515
516 /**
517 * Get the list of allowed comment types from settings
518 *
519 * @return array Array of allowed comment types
520 */
521 private function get_allowed_comment_types() {
522 if (!isset($this->options['allowed_comment_types']) || !is_array($this->options['allowed_comment_types'])) {
523 return array(); // Default: all special comment types disabled
524 }
525 return $this->options['allowed_comment_types'];
526 }
527
528 /**
529 * Check if any comment types are enabled in the allowlist
530 *
531 * @return bool True if there are allowed comment types, false otherwise
532 */
533 private function has_allowed_comment_types() {
534 $allowed_types = $this->get_allowed_comment_types();
535 return !empty($allowed_types);
536 }
537
538 /**
539 * Check if a specific comment type is allowed (enabled in the allowlist)
540 *
541 * @param string $comment_type The comment type to check
542 * @return bool True if the comment type is allowed, false otherwise
543 */
544 private function is_comment_type_allowed($comment_type) {
545 $allowed_types = $this->get_allowed_comment_types();
546 return in_array($comment_type, $allowed_types, true);
547 }
548
549 /**
550 * Get available comment type options for the "Enable Certain Comment Types" UI
551 *
552 * This function returns a list of known special comment types that users can enable,
553 * regardless of whether any comments of those types currently exist in the database.
554 *
555 * IMPORTANT: WordPress does not provide a formal API for registering or retrieving
556 * comment types (unlike post types with get_post_types()). Comment types are simply
557 * arbitrary string values stored in the wp_comments table. Therefore, we maintain
558 * a curated list of known special comment types that plugins commonly use.
559 *
560 * This function returns only predefined known types plus any types added via the
561 * 'disable_comments_known_comment_types' filter hook.
562 *
563 * @return array Associative array of comment_type => label
564 */
565 public function get_available_comment_type_options() {
566 // Predefined known special comment types with descriptive labels
567 // These are shown even if no comments of these types exist yet in the database
568 //
569 // Note: WordPress does not have a formal comment type registration API,
570 // so this list is maintained manually based on common plugin usage.
571 $known_types = array(
572 'note' => __('Notes - WordPress 6.9+ (note)', 'disable-comments'),
573 );
574
575 /**
576 * Filter the list of known comment types shown in the "Enable Certain Comment Types" UI
577 *
578 * Plugins can add their own comment types to this list so users can enable them
579 * even before any comments of those types exist in the database.
580 *
581 * Example:
582 * add_filter( 'disable_comments_known_comment_types', function( $types ) {
583 * $types['my_custom_type'] = __( 'My Custom Comment Type', 'my-plugin' );
584 * return $types;
585 * } );
586 *
587 * @param array $known_types Associative array of comment_type => label
588 */
589 return apply_filters('disable_comments_known_comment_types', $known_types);
590 }
591
592 /**
593 * Check if a REST API request is for an allowed comment type
594 *
595 * @param WP_REST_Request $request The REST API request object
596 * @return bool True if the request is for an allowed comment type, false otherwise
597 */
598 private function is_allowed_comment_type_request($request = null) {
599 $comment_type = null;
600
601 // Check if we have a request object
602 if (!$request) {
603 // Check global $_REQUEST for type parameter
604 if (isset($_REQUEST['type'])) {
605 $comment_type = sanitize_text_field(wp_unslash($_REQUEST['type']));
606 }
607 // Check if we're in a REST API context
608 elseif (defined('REST_REQUEST') && REST_REQUEST) {
609 global $wp;
610 if (isset($wp->query_vars['type'])) {
611 $comment_type = sanitize_text_field($wp->query_vars['type']);
612 }
613 }
614 } else {
615 // Check the request object for type parameter
616 $type = $request->get_param('type');
617 if ($type) {
618 $comment_type = $type;
619 }
620
621 // Check the request body for type parameter (for POST requests)
622 if (!$comment_type) {
623 $body = $request->get_body_params();
624 if (isset($body['type'])) {
625 $comment_type = $body['type'];
626 }
627 }
628
629 // Check JSON body for type parameter
630 if (!$comment_type) {
631 $json = $request->get_json_params();
632 if (isset($json['type'])) {
633 $comment_type = $json['type'];
634 }
635 }
636
637 // For UPDATE requests (PUT/PATCH), check if the existing comment is an allowed type
638 // WordPress doesn't send the type parameter when updating, only the ID and content
639 if (!$comment_type) {
640 $comment_id = $request->get_param('id');
641 if ($comment_id) {
642 $comment = get_comment($comment_id);
643 if ($comment && isset($comment->comment_type)) {
644 $comment_type = $comment->comment_type;
645 }
646 }
647 }
648
649 // For DELETE requests, extract comment ID from the route path
650 // The comment ID is only in the URL (e.g., /wp/v2/comments/123), not in request params
651 if (!$comment_type && $request->is_method('DELETE')) {
652 $route_parts = explode('/', $request->get_route());
653 $comment_id = end($route_parts);
654
655 // Ensure we have a numeric comment ID
656 if (is_numeric($comment_id)) {
657 $comment = get_comment((int) $comment_id);
658 if ($comment && isset($comment->comment_type)) {
659 $comment_type = $comment->comment_type;
660 }
661 }
662 }
663 }
664
665 // Check if the comment type is in the allowlist
666 if ($comment_type && $this->is_comment_type_allowed($comment_type)) {
667 return true;
668 }
669
670 return false;
671 }
672
673 /**
674 * Issue a 403 for all comment feed requests.
675 */
676 public function filter_query() {
677 if (is_comment_feed()) {
678 wp_die(esc_html__('Comments are closed.', 'disable-comments'), '', array('response' => 403));
679 }
680 }
681
682 /**
683 * Remove comment links from the admin bar.
684 */
685 public function filter_admin_bar() {
686 if (is_admin_bar_showing()) {
687 // Remove comments links from admin bar.
688 remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60);
689 if (is_multisite()) {
690 add_action('admin_bar_menu', array($this, 'remove_network_comment_links'), 500);
691 }
692 }
693 }
694
695 /**
696 * Remove the comments endpoint for the REST API
697 * But allow WordPress 6.9+ block notes (type=note) to work
698 */
699 public function filter_rest_endpoints($endpoints) {
700 // Don't remove endpoints entirely - instead we'll use permission callbacks
701 // and other filters to block regular comments while allowing notes
702
703 // We still need to add a filter to block non-note requests
704 // This is handled by rest_pre_dispatch filter added in init_filters
705
706 return $endpoints;
707 }
708
709 /**
710 * Filter REST API comment requests to block comments except allowed types
711 *
712 * @param mixed $result Response to replace the requested version with
713 * @param WP_REST_Server $server Server instance
714 * @param WP_REST_Request $request Request used to generate the response
715 * @return mixed
716 */
717 public function filter_rest_comment_dispatch($result, $server, $request) {
718 // Only filter comment-related routes
719 $route = $request->get_route();
720 if (strpos($route, '/wp/v2/comments') === false) {
721 return $result;
722 }
723
724 // Allow requests for comment types in the allowlist to pass through
725 if ($this->is_allowed_comment_type_request($request)) {
726 return $result;
727 }
728
729 // Block all other comment requests
730 return new WP_Error(
731 'rest_comment_disabled',
732 __('Comments are disabled.', 'disable-comments'),
733 array('status' => 403)
734 );
735 }
736
737 /**
738 * Filter comment queries in REST API to allow only allowed comment types
739 *
740 * @param array $prepared_args Array of arguments for WP_Comment_Query
741 * @param WP_REST_Request $request The REST API request
742 * @return array
743 */
744 public function filter_rest_comment_query($prepared_args, $request) {
745 // If this is a request for an allowed comment type, allow it
746 if ($this->is_allowed_comment_type_request($request)) {
747 return $prepared_args;
748 }
749
750 // For non-allowed requests, return empty results
751 // by setting an impossible condition
752 $prepared_args['comment__in'] = array(0);
753
754 return $prepared_args;
755 }
756
757 /**
758 * Determines if scripts should be enqueued
759 */
760 public function filter_gutenberg_blocks($hook) {
761 global $post;
762 if ($this->is_remove_everywhere() || (isset($post->post_type) && $this->is_post_type_disabled($post->post_type))) {
763 return $this->disable_comments_script();
764 }
765 }
766
767 /**
768 * Enqueues scripts
769 */
770 public function disable_comments_script() {
771 wp_enqueue_script('disable-comments-gutenberg', plugin_dir_url(__FILE__) . 'assets/js/disable-comments.js', array(), DC_VERSION, true);
772 }
773
774 /**
775 * Enqueues Scripts for Settings Page
776 */
777 public function settings_page_assets($hook_suffix) {
778 if (
779 $hook_suffix === 'settings_page_' . DC_PLUGIN_SLUG ||
780 $hook_suffix === 'options-general_' . DC_PLUGIN_SLUG
781 ) {
782 // css
783 wp_enqueue_style('sweetalert2', DC_ASSETS_URI . 'css/sweetalert2.min.css', [], DC_VERSION);
784 // wp_enqueue_style('pagination', DC_ASSETS_URI . 'css/pagination.css', [], false);
785 wp_enqueue_style('disable-comments-style', DC_ASSETS_URI . 'css/style.css', [], DC_VERSION);
786 wp_enqueue_style('select2', DC_ASSETS_URI . 'css/select2.min.css', [], DC_VERSION);
787 // js
788 wp_enqueue_script('sweetalert2', DC_ASSETS_URI . 'js/sweetalert2.all.min.js', array('jquery'), DC_VERSION, true);
789 wp_enqueue_script('pagination', DC_ASSETS_URI . 'js/pagination.min.js', array('jquery'), DC_VERSION, true);
790 wp_enqueue_script('select2', DC_ASSETS_URI . 'js/select2.min.js', array('jquery'), DC_VERSION, true);
791 wp_enqueue_script('disable-comments-scripts', DC_ASSETS_URI . 'js/disable-comments-settings-scripts.js', array('jquery', 'select2', 'pagination', 'sweetalert2', 'wp-i18n'), DC_VERSION, true);
792 wp_localize_script(
793 'disable-comments-scripts',
794 'disableCommentsObj',
795 array(
796 'save_action' => 'disable_comments_save_settings',
797 'delete_action' => 'disable_comments_delete_comments',
798 'settings_URI' => $this->settings_page_url(),
799 '_nonce' => wp_create_nonce('disable_comments_save_settings')
800 )
801 );
802 wp_set_script_translations('disable-comments-scripts', 'disable-comments');
803 } else {
804 // notice css
805 wp_enqueue_style('disable-comments-notice', DC_ASSETS_URI . 'css/notice.css', [], DC_VERSION);
806 }
807 }
808
809 /**
810 * Remove comment links from the admin bar in a multisite network.
811 */
812 public function remove_network_comment_links($wp_admin_bar) {
813 if ($this->networkactive && is_user_logged_in()) {
814 foreach ((array) $wp_admin_bar->user->blogs as $blog) {
815 $wp_admin_bar->remove_menu('blog-' . $blog->userblog_id . '-c');
816 }
817 } else {
818 // We have no way to know whether the plugin is active on other sites, so only remove this one.
819 $wp_admin_bar->remove_menu('blog-' . get_current_blog_id() . '-c');
820 }
821 }
822
823 public function discussion_notice() {
824 $disabled_post_types = $this->get_disabled_post_types();
825 if (get_current_screen()->id == 'options-discussion' && !empty($disabled_post_types)) {
826 $names_escaped = array();
827 foreach ($disabled_post_types as $type) {
828 $names_escaped[$type] = esc_html(get_post_type_object($type)->labels->name);
829 }
830
831 // translators: %s: disabled post types.
832 echo '<div class="notice notice-warning"><p>' . sprintf(esc_html__('Note: The <em>Disable Comments</em> plugin is currently active, and comments are completely disabled on: %s. Many of the settings below will not be applicable for those post types.', 'disable-comments'), implode(esc_html__(', ', 'disable-comments'), $names_escaped)) . '</p></div>';
833 }
834 }
835
836 /**
837 * Return context-aware settings page URL
838 */
839 private function settings_page_url() {
840 $base = $this->networkactive && is_network_admin() ? network_admin_url('settings.php') : admin_url('options-general.php');
841 return add_query_arg('page', DC_PLUGIN_SLUG, $base);
842 }
843
844 /**
845 * Return context-aware tools page URL
846 */
847 private function tools_page_url() {
848 $base = $this->networkactive && is_network_admin() ? network_admin_url('settings.php') : admin_url('tools.php');
849 return add_query_arg('page', 'disable_comments_tools', $base);
850 }
851
852
853 public function setup_notice() {
854 $current_screen = get_current_screen()->id;
855 if (!in_array($current_screen, ['dashboard-network', 'dashboard'])) {
856 return;
857 }
858 $hascaps = $this->networkactive && is_network_admin() ? current_user_can('manage_network_plugins') : current_user_can('manage_options');
859 if ($this->networkactive && !is_network_admin() && !$this->options['sitewide_settings']) {
860 $hascaps = false;
861 }
862 if ($hascaps) {
863 $this->setup_notice_flag = true;
864 // translators: %s: URL to Disabled Comment settings page.
865 $html = sprintf(__('The <strong>Disable Comments</strong> plugin is active, but isn\'t configured to do anything yet. Visit the <a href="%s">configuration page</a> to choose which post types to disable comments on.', 'disable-comments'), esc_attr($this->settings_page_url()));
866 // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
867 echo wp_kses_post('<div class="notice dc-text__block disable__comment__alert mb30"><img height="30" src="' . esc_url(DC_ASSETS_URI . 'img/icon-logo.png') . '" alt=""><p>' . $html . '</p></div>');
868 }
869 }
870
871 public function filter_admin_menu() {
872 global $pagenow;
873
874 if (empty($this->options['show_existing_comments'])) {
875 if ($pagenow == 'comment.php' || $pagenow == 'edit-comments.php') {
876 wp_die(esc_html__('Comments are closed.', 'disable-comments'), '', array('response' => 403));
877 }
878
879 remove_menu_page('edit-comments.php');
880 }
881
882 if (!$this->discussion_settings_allowed()) {
883 if ($pagenow == 'options-discussion.php') {
884 wp_die(esc_html__('Comments are closed.', 'disable-comments'), '', array('response' => 403));
885 }
886
887 remove_submenu_page('options-general.php', 'options-discussion.php');
888 }
889 }
890
891 public function filter_dashboard() {
892 remove_meta_box('dashboard_recent_comments', 'dashboard', 'normal');
893 }
894
895 public function admin_css() {
896 echo '<style>
897 #dashboard_right_now .comment-count,
898 #dashboard_right_now .comment-mod-count,
899 #latest-comments,
900 #welcome-panel .welcome-comments,
901 .user-comment-shortcuts-wrap {
902 display: none !important;
903 }
904 </style>';
905 }
906
907 public function filter_existing_comments($comments, $post_id) {
908 $post_type = get_post_type($post_id);
909 $comments_disabled = $this->is_remove_everywhere() || $this->is_post_type_disabled($post_type);
910
911 // If comments are disabled but show_existing_comments is enabled, return existing comments
912 if ($comments_disabled && !empty($this->options['show_existing_comments'])) {
913 $comments_disabled = false;
914 }
915
916 // If comments are disabled, filter out regular comments but keep allowed comment types
917 if ($comments_disabled && !empty($comments)) {
918 $filtered_comments = array();
919 foreach ($comments as $comment) {
920 // Keep comment types that are in the allowlist even when comments are disabled
921 if (isset($comment->comment_type) && $this->is_comment_type_allowed($comment->comment_type)) {
922 $filtered_comments[] = $comment;
923 }
924 }
925 return $filtered_comments;
926 }
927
928 // Default behavior: return all comments if not disabled
929 return $comments;
930 }
931
932 public function filter_comment_status($open, $post_id) {
933 $post_type = get_post_type($post_id);
934 return ($this->is_remove_everywhere() || $this->is_post_type_disabled($post_type) ? false : $open);
935 }
936
937 public function filter_comments_number($count, $post_id) {
938 $post_type = get_post_type($post_id);
939 $comments_disabled = $this->is_remove_everywhere() || $this->is_post_type_disabled($post_type);
940
941 // If comments are disabled but show_existing_comments is enabled, return actual count
942 if ($comments_disabled && !empty($this->options['show_existing_comments'])) {
943 return $count;
944 }
945
946 // If comments are disabled but there are allowed comment types, count only those types
947 if ($comments_disabled && $this->has_allowed_comment_types()) {
948 return $this->count_allowed_comment_types($post_id);
949 }
950
951 return $comments_disabled ? 0 : $count;
952 }
953
954 /**
955 * Count comments of allowed types for a specific post
956 *
957 * @param int $post_id The post ID
958 * @return int The count of comments matching allowed types
959 */
960 private function count_allowed_comment_types($post_id) {
961 $allowed_types = $this->get_allowed_comment_types();
962 if (empty($allowed_types)) {
963 return 0;
964 }
965
966 $comments = get_comments(array(
967 'post_id' => $post_id,
968 'type__in' => $allowed_types,
969 'status' => 'approve',
970 'count' => true,
971 ));
972
973 return (int) $comments;
974 }
975
976 public function disable_rc_widget() {
977 unregister_widget('WP_Widget_Recent_Comments');
978 /**
979 * The widget has added a style action when it was constructed - which will
980 * still fire even if we now unregister the widget... so filter that out
981 */
982 add_filter('show_recent_comments_widget_style', '__return_false');
983 }
984
985 public function set_plugin_meta($links, $file) {
986 static $plugin;
987 $plugin = plugin_basename(__FILE__);
988 if ($file == $plugin) {
989 $links[] = '<a href="https://github.com/WPDevelopers/disable-comments">GitHub</a>';
990 }
991 return $links;
992 }
993
994 /**
995 * Add links to Settings page
996 */
997 public function plugin_actions_links($links, $file) {
998 static $plugin;
999 $plugin = plugin_basename(__FILE__);
1000 if ($file == $plugin && current_user_can('manage_options')) {
1001 array_unshift(
1002 $links,
1003 sprintf('<a href="%s">%s</a>', esc_attr($this->settings_page_url()), __('Settings', 'disable-comments')),
1004 sprintf('<a href="%s">%s</a>', esc_attr($this->tools_page_url()), __('Tools', 'disable-comments'))
1005 );
1006 }
1007
1008 return $links;
1009 }
1010
1011 public function settings_menu() {
1012 $title = _x('Disable Comments', 'settings menu title', 'disable-comments');
1013 if ($this->networkactive && is_network_admin()) {
1014 add_submenu_page('settings.php', $title, $title, 'manage_network_plugins', DC_PLUGIN_SLUG, array($this, 'settings_page'));
1015 } elseif (!$this->networkactive || $this->options['sitewide_settings']) {
1016 add_submenu_page('options-general.php', $title, $title, 'manage_options', DC_PLUGIN_SLUG, array($this, 'settings_page'));
1017 }
1018 }
1019
1020 public function tools_menu() {
1021 $title = __('Delete Comments', 'disable-comments');
1022 $hook = '';
1023 if ($this->networkactive && is_network_admin()) {
1024 $hook = add_submenu_page('settings.php', $title, $title, 'manage_network_plugins', 'disable_comments_tools', array($this, 'tools_page'));
1025 } elseif (!$this->networkactive || $this->options['sitewide_settings']) {
1026 $hook = add_submenu_page('tools.php', $title, $title, 'manage_options', 'disable_comments_tools', array($this, 'tools_page'));
1027 }
1028 add_action('load-' . $hook, array($this, 'redirectToMainSettingsPage'));
1029 }
1030
1031 public function redirectToMainSettingsPage() {
1032 wp_safe_redirect($this->settings_page_url() . '#delete');
1033 exit;
1034 }
1035
1036 public function get_all_comments_number() {
1037 global $wpdb;
1038 if (is_network_admin() && function_exists('get_sites') && class_exists('WP_Site_Query')) {
1039 $count = 0;
1040 $sites = get_sites([
1041 'number' => 0,
1042 'fields' => 'ids',
1043 ]);
1044 foreach ($sites as $blog_id) {
1045 switch_to_blog($blog_id);
1046 $count += $this->__get_comment_count();
1047 restore_current_blog();
1048 }
1049 return $count;
1050 } else {
1051 return $this->__get_comment_count();
1052 }
1053 }
1054
1055 public function get_all_comment_types($exclude_allowed = true) {
1056 if ($this->networkactive && is_network_admin() && function_exists('get_sites')) {
1057 $comment_types = [];
1058 $sites = get_sites([
1059 'number' => 0,
1060 'fields' => 'ids',
1061 ]);
1062 foreach ($sites as $blog_id) {
1063 switch_to_blog($blog_id);
1064 $comment_types = array_merge($this->_get_all_comment_types($exclude_allowed), $comment_types);
1065 restore_current_blog();
1066 }
1067 return $comment_types;
1068 } else {
1069 return $this->_get_all_comment_types($exclude_allowed);
1070 }
1071 }
1072 public function _get_all_comment_types($exclude_allowed = true) {
1073 global $wpdb;
1074 $commenttypes = array();
1075 // we need fresh data in every call.
1076 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery -- We need to count comments across multiple sites
1077 $commenttypes_query = $wpdb->get_results("SELECT DISTINCT comment_type FROM $wpdb->comments", ARRAY_A);
1078 if (!empty($commenttypes_query) && is_array($commenttypes_query)) {
1079 foreach ($commenttypes_query as $entry) {
1080 $value = $entry['comment_type'];
1081 // Exclude comment types that are in the allowlist from deletable comment types
1082 // These are protected and should not appear in the "Delete Certain Comment Types" interface
1083 if ($exclude_allowed && $this->is_comment_type_allowed($value)) {
1084 continue;
1085 }
1086 if ('' === $value) {
1087 $commenttypes['default'] = __('Default (no type)', 'disable-comments');
1088 } else {
1089 $commenttypes[$value] = ucwords(str_replace('_', ' ', $value)) . ' (' . $value . ')';
1090 }
1091 }
1092 }
1093 return $commenttypes;
1094 }
1095
1096 public function get_all_post_types($network = false) {
1097 $typeargs = array('public' => true);
1098 if ($network || $this->networkactive && is_network_admin()) {
1099 $typeargs['_builtin'] = true; // stick to known types for network.
1100 }
1101 $types = get_post_types($typeargs, 'objects');
1102 foreach (array_keys($types) as $type) {
1103 if (!in_array($type, $this->modified_types) && !post_type_supports($type, 'comments')) { // the type doesn't support comments anyway.
1104 unset($types[$type]);
1105 }
1106 }
1107 return $types;
1108 }
1109
1110 public function get_roles($selected) {
1111 $roles = [
1112 [
1113 "id" => 'logged-out-users',
1114 "text" => __('Logged out users', 'disable-comments'),
1115 "selected" => in_array('logged-out-users', (array) $selected),
1116 ]
1117 ];
1118 $editable_roles = array_reverse(get_editable_roles());
1119 foreach ($editable_roles as $role => $details) {
1120 $roles[] = [
1121 "id" => esc_attr($role),
1122 "text" => translate_user_role($details['name']),
1123 "selected" => in_array($role, (array) $selected),
1124 ];
1125 }
1126 return $roles;
1127 }
1128
1129 public function tools_page() {
1130 return;
1131 }
1132
1133 public function settings_page() {
1134 // if( isset( $_GET['cancel'] ) && trim( $_GET['cancel'] ) === 'setup' ){
1135 // $this->update_option('dc_setup_screen_seen', true);
1136 // }
1137 $avatar_status = '-1';
1138 if ($this->is_network_admin()) {
1139 $show_avatars = [];
1140 $sites = get_sites([
1141 'number' => 0,
1142 'fields' => 'ids',
1143 ]);
1144 foreach ($sites as $blog_id) {
1145 switch_to_blog($blog_id);
1146 $show_avatars[] = (int) get_option('show_avatars', '0');
1147 restore_current_blog();
1148 }
1149 if (count($show_avatars) == array_sum($show_avatars)) {
1150 $avatar_status = '0';
1151 } elseif (0 == array_sum($show_avatars)) {
1152 $avatar_status = '1';
1153 }
1154 }
1155
1156 include_once DC_PLUGIN_VIEWS_PATH . 'settings.php';
1157 }
1158
1159 public function get_sub_sites() {
1160 $nonce = (isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '');
1161 if (!wp_verify_nonce($nonce, 'disable_comments_save_settings')) {
1162 wp_send_json(['data' => [], 'totalNumber' => 0]);
1163 }
1164
1165 $_sub_sites = [];
1166 $type = isset($_GET['type']) ? sanitize_text_field(wp_unslash($_GET['type'])) : 'disabled';
1167 $search = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : '';
1168 $pageSize = isset($_GET['pageSize']) ? sanitize_text_field(wp_unslash($_GET['pageSize'])) : 50;
1169 $pageNumber = isset($_GET['pageNumber']) ? sanitize_text_field(wp_unslash($_GET['pageNumber'])) : 1;
1170 $offset = ($pageNumber - 1) * $pageSize;
1171 $sub_sites = get_sites([
1172 'number' => $pageSize,
1173 'offset' => $offset,
1174 'search' => $search,
1175 'fields' => 'ids',
1176 ]);
1177 $totalNumber = get_sites([
1178 // 'number' => $pageSize,
1179 // 'offset' => $offset,
1180 'search' => $search,
1181 'count' => true,
1182 ]);
1183
1184 if ($type == 'disabled') {
1185 $disabled_site_options = isset($this->options['disabled_sites']) ? $this->options['disabled_sites'] : [];
1186 } else { // if($type == 'delete')
1187 $disabled_site_options = $this->get_disabled_sites(true);
1188 }
1189
1190 foreach ($sub_sites as $sub_site_id) {
1191 $blog = get_blog_details($sub_site_id);
1192 $is_checked = checked(!empty($disabled_site_options["site_$sub_site_id"]), true, false);
1193 $_sub_sites[] = [
1194 'site_id' => $sub_site_id,
1195 'is_checked' => $is_checked,
1196 'blogname' => $blog->blogname,
1197 ];
1198 }
1199 wp_send_json(['data' => $_sub_sites, 'totalNumber' => $totalNumber]);
1200 }
1201
1202 public function get_form_array_escaped($_args = array()) {
1203 $formArray = [];
1204 if (!empty($_args)) {
1205 $formArray = wp_parse_args($_args);
1206 }
1207 // nonce is verified in the calling function
1208 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1209 else if (isset($_POST['data'])) {
1210 // need to use wp_parse_args before map_deep sanitize_text_field
1211 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
1212 $formArray = map_deep(wp_parse_args(wp_unslash($_POST['data'])), 'sanitize_text_field');
1213 }
1214 return $formArray;
1215 }
1216
1217 public function disable_comments_settings($_args = array()) {
1218 $nonce = (isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '');
1219 if (($this->is_CLI && !empty($_args)) || wp_verify_nonce($nonce, 'disable_comments_save_settings')) {
1220
1221 $formArray = $this->get_form_array_escaped($_args);
1222
1223 $old_options = $this->options;
1224 $this->options = [];
1225 if ($this->is_CLI) {
1226 $this->options = $old_options;
1227 }
1228
1229 $this->options['is_network_admin'] = isset($formArray['is_network_admin']) && $formArray['is_network_admin'] == '1' ? true : false;
1230
1231 if (!empty($this->options['is_network_admin']) && function_exists('get_sites') && empty($formArray['sitewide_settings'])) {
1232 $formArray['disabled_sites'] = isset($formArray['disabled_sites']) ? $formArray['disabled_sites'] : [];
1233 $this->options['disabled_sites'] = isset($old_options['disabled_sites']) ? $old_options['disabled_sites'] : [];
1234 $this->options['disabled_sites'] = array_merge($this->options['disabled_sites'], $formArray['disabled_sites']);
1235 } elseif (!empty($this->options['is_network_admin']) && !empty($formArray['sitewide_settings'])) {
1236 $this->options['disabled_sites'] = $old_options['disabled_sites'];
1237 }
1238
1239 if (isset($formArray['mode'])) {
1240 $this->options['remove_everywhere'] = (sanitize_text_field($formArray['mode']) == 'remove_everywhere');
1241 }
1242 $post_types = $this->get_all_post_types($this->options['is_network_admin']);
1243
1244 if ($this->options['remove_everywhere']) {
1245 $disabled_post_types = array_keys($post_types);
1246 } else {
1247 $disabled_post_types = (isset($formArray['disabled_types']) ? array_map('sanitize_key', (array) $formArray['disabled_types']) : ($this->is_CLI && isset($this->options['disabled_post_types']) ? $this->options['disabled_post_types'] : []));
1248 }
1249
1250 $disabled_post_types = array_intersect($disabled_post_types, array_keys($post_types));
1251 $this->options['disabled_post_types'] = $disabled_post_types;
1252
1253 // Extra custom post types.
1254 if ($this->networkactive && isset($formArray['extra_post_types'])) {
1255 $extra_post_types = array_filter(array_map('sanitize_key', explode(',', $formArray['extra_post_types'])));
1256 $this->options['extra_post_types'] = array_diff($extra_post_types, array_keys($post_types)); // Make sure we don't double up builtins.
1257 }
1258
1259 if (isset($formArray['sitewide_settings'])) {
1260 update_site_option('disable_comments_sitewide_settings', $formArray['sitewide_settings']);
1261 }
1262
1263 if (isset($formArray['disable_avatar'])) {
1264 if ($this->is_network_admin()) {
1265 if ($formArray['disable_avatar'] == '0' || $formArray['disable_avatar'] == '1') {
1266 $sites = get_sites([
1267 'number' => 0,
1268 'fields' => 'ids',
1269 ]);
1270 foreach ($sites as $blog_id) {
1271 switch_to_blog($blog_id);
1272 update_option('show_avatars', (bool) !$formArray['disable_avatar']);
1273 restore_current_blog();
1274 }
1275 }
1276 } else {
1277 update_option('show_avatars', (bool) !$formArray['disable_avatar']);
1278 }
1279 }
1280
1281 if (isset($formArray['enable_exclude_by_role'])) {
1282 $this->options['enable_exclude_by_role'] = $formArray['enable_exclude_by_role'];
1283 }
1284 if (isset($formArray['exclude_by_role'])) {
1285 $this->options['exclude_by_role'] = $formArray['exclude_by_role'];
1286 }
1287
1288 // xml rpc
1289 $this->options['remove_xmlrpc_comments'] = (isset($formArray['remove_xmlrpc_comments']) ? intval($formArray['remove_xmlrpc_comments']) : ($this->is_CLI && isset($this->options['remove_xmlrpc_comments']) ? $this->options['remove_xmlrpc_comments'] : 0));
1290 // rest api comments
1291 $this->options['remove_rest_API_comments'] = (isset($formArray['remove_rest_API_comments']) ? intval($formArray['remove_rest_API_comments']) : ($this->is_CLI && isset($this->options['remove_rest_API_comments']) ? $this->options['remove_rest_API_comments'] : 0));
1292 // show existing comments
1293 $this->options['show_existing_comments'] = (isset($formArray['show_existing_comments']) ? (bool) $formArray['show_existing_comments'] : ($this->is_CLI && isset($this->options['show_existing_comments']) ? $this->options['show_existing_comments'] : false));
1294
1295 // allowed comment types (opt-in allowlist)
1296 if (isset($formArray['allowed_comment_types']) && is_array($formArray['allowed_comment_types'])) {
1297 // Sanitize and validate the allowed comment types
1298 $this->options['allowed_comment_types'] = array_map('sanitize_key', $formArray['allowed_comment_types']);
1299 } else {
1300 // Default: empty array (all special comment types disabled)
1301 $this->options['allowed_comment_types'] = array();
1302 }
1303
1304 $this->options['db_version'] = self::DB_VERSION;
1305 $this->options['settings_saved'] = true;
1306 // save settings
1307 $this->update_options();
1308 }
1309 if (!$this->is_CLI) {
1310 wp_send_json_success(array('message' => __('Saved', 'disable-comments')));
1311 wp_die();
1312 }
1313 }
1314
1315 public function is_configured() {
1316 $disabled_post_types = $this->get_disabled_post_types();
1317
1318 if (empty($disabled_post_types) && empty($this->options['remove_everywhere']) && empty($this->options['remove_rest_API_comments']) && empty($this->options['remove_xmlrpc_comments'])) {
1319 return false;
1320 }
1321 return true;
1322 }
1323
1324 public function delete_comments_settings($_args = array()) {
1325 global $deletedPostTypeNames;
1326 $log = '';
1327 $nonce = (isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '');
1328
1329 if (($this->is_CLI && !empty($_args)) || wp_verify_nonce($nonce, 'disable_comments_save_settings')) {
1330 $formArray = $this->get_form_array_escaped($_args);
1331
1332 if (!empty($formArray['is_network_admin']) && function_exists('get_sites') && class_exists('WP_Site_Query')) {
1333 $sites = get_sites([
1334 'number' => 0,
1335 'fields' => 'ids',
1336 ]);
1337 foreach ($sites as $blog_id) {
1338 // $formArray['disabled_sites'] ids don't include "site_" prefix.
1339 if (!empty($formArray['disabled_sites']) && !empty($formArray['disabled_sites']["site_$blog_id"])) {
1340 switch_to_blog($blog_id);
1341 $log = $this->delete_comments($_args);
1342 restore_current_blog();
1343 }
1344 }
1345 } else {
1346 $log = $this->delete_comments($_args);
1347 }
1348 }
1349 // message
1350 $deletedPostTypeNames = array_unique((array) $deletedPostTypeNames);
1351 $message = (count($deletedPostTypeNames) == 0 ? $log . '.' : $log . ' for ' . implode(", ", $deletedPostTypeNames) . '.');
1352 if (!$this->is_CLI) {
1353 wp_send_json_success(array('message' => $message));
1354 wp_die();
1355 } else {
1356 return $log;
1357 }
1358 }
1359
1360 private function delete_comments($_args) {
1361 global $wpdb;
1362 global $deletedPostTypeNames;
1363
1364 $formArray = $this->get_form_array_escaped($_args);
1365
1366 $types = $this->get_all_post_types(!empty($formArray['is_network_admin']));
1367 $commenttypes = $this->get_all_comment_types();
1368 $log = "";
1369 // comments delete
1370 if (isset($formArray['delete_mode'])) {
1371 if ($formArray['delete_mode'] == 'delete_everywhere') {
1372 // Delete all comment metadata except for allowed comment types
1373 $allowed_types = $this->get_allowed_comment_types();
1374
1375 if (empty($allowed_types)) {
1376 // No allowed types, delete all comments
1377 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1378 $wpdb->query("DELETE FROM $wpdb->commentmeta");
1379 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1380 $wpdb->query("DELETE FROM $wpdb->comments");
1381 } else {
1382 // Build exclusion query for allowed comment types
1383 $placeholders = implode(', ', array_fill(0, count($allowed_types), '%s'));
1384
1385 // Delete comment metadata
1386 $query = $wpdb->prepare(
1387 "DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID WHERE comments.comment_type NOT IN ($placeholders)",
1388 $allowed_types
1389 );
1390 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1391 $wpdb->query($query);
1392
1393 // Delete comments
1394 $query = $wpdb->prepare(
1395 "DELETE FROM $wpdb->comments WHERE comment_type NOT IN ($placeholders)",
1396 $allowed_types
1397 );
1398 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1399 $wpdb->query($query);
1400 }
1401
1402 // Update comment counts
1403 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1404 $wpdb->query("UPDATE $wpdb->posts SET comment_count = 0");
1405 $this->optimize_table($wpdb->commentmeta);
1406 $this->optimize_table($wpdb->comments);
1407 $log = __('All comments have been deleted', 'disable-comments');
1408 } elseif ($formArray['delete_mode'] == 'selected_delete_types') {
1409 $delete_post_types = empty($formArray['delete_types']) ? array() : (array) $formArray['delete_types'];
1410 $delete_post_types = array_intersect($delete_post_types, array_keys($types));
1411
1412 // Extra custom post types.
1413 if ($this->networkactive && !empty($formArray['delete_extra_post_types'])) {
1414 $delete_extra_post_types = array_filter(array_map('sanitize_key', explode(',', $formArray['delete_extra_post_types'])));
1415 $delete_extra_post_types = array_diff($delete_extra_post_types, array_keys($types)); // Make sure we don't double up builtins.
1416 $delete_post_types = array_merge($delete_post_types, $delete_extra_post_types);
1417 }
1418
1419 if (!empty($delete_post_types)) {
1420 // Loop through post_types and remove comments/meta (excluding allowed comment types) and set posts comment_count to 0.
1421 $allowed_types = $this->get_allowed_comment_types();
1422
1423 foreach ($delete_post_types as $delete_post_type) {
1424 if (empty($allowed_types)) {
1425 // No allowed types, delete all comments for this post type
1426 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1427 $wpdb->query($wpdb->prepare("DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID INNER JOIN $wpdb->posts posts ON comments.comment_post_ID=posts.ID WHERE posts.post_type = %s", $delete_post_type));
1428 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1429 $wpdb->query($wpdb->prepare("DELETE comments FROM $wpdb->comments comments INNER JOIN $wpdb->posts posts ON comments.comment_post_ID=posts.ID WHERE posts.post_type = %s", $delete_post_type));
1430 } else {
1431 // Build exclusion query for allowed comment types
1432 $placeholders = implode(', ', array_fill(0, count($allowed_types), '%s'));
1433 $params = array_merge(array($delete_post_type), $allowed_types);
1434
1435 // Delete comment metadata
1436 $query = $wpdb->prepare(
1437 "DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID INNER JOIN $wpdb->posts posts ON comments.comment_post_ID=posts.ID WHERE posts.post_type = %s AND comments.comment_type NOT IN ($placeholders)",
1438 $params
1439 );
1440 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1441 $wpdb->query($query);
1442
1443 // Delete comments
1444 $query = $wpdb->prepare(
1445 "DELETE comments FROM $wpdb->comments comments INNER JOIN $wpdb->posts posts ON comments.comment_post_ID=posts.ID WHERE posts.post_type = %s AND comments.comment_type NOT IN ($placeholders)",
1446 $params
1447 );
1448 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1449 $wpdb->query($query);
1450 }
1451
1452 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1453 $wpdb->query($wpdb->prepare("UPDATE $wpdb->posts SET comment_count = 0 WHERE post_author != 0 AND post_type = %s", $delete_post_type));
1454
1455 $post_type_object = get_post_type_object($delete_post_type);
1456 $post_type_label = $post_type_object ? $post_type_object->labels->name : $delete_post_type;
1457 $deletedPostTypeNames[] = $post_type_label;
1458 }
1459
1460 $this->optimize_table($wpdb->commentmeta);
1461 $this->optimize_table($wpdb->comments);
1462 $log = __('All comments have been deleted', 'disable-comments');
1463 }
1464 } elseif ($formArray['delete_mode'] == 'selected_delete_comment_types') {
1465 $delete_comment_types = empty($formArray['delete_comment_types']) ? array() : (array) $formArray['delete_comment_types'];
1466 $delete_comment_types = array_intersect($delete_comment_types, array_keys($commenttypes));
1467
1468 if (!empty($delete_comment_types)) {
1469 // Loop through comment_types and remove comments/meta and set posts comment_count to 0.
1470 foreach ($delete_comment_types as $delete_comment_type) {
1471 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1472 $wpdb->query($wpdb->prepare("DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID WHERE comments.comment_type = %s", $delete_comment_type));
1473 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1474 $wpdb->query($wpdb->prepare("DELETE comments FROM $wpdb->comments comments WHERE comments.comment_type = %s", $delete_comment_type));
1475 $deletedPostTypeNames[] = $commenttypes[$delete_comment_type];
1476 }
1477
1478 // Update comment_count on post_types
1479 foreach ($types as $key => $value) {
1480 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1481 $comment_count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(comments.comment_ID) FROM $wpdb->comments comments INNER JOIN $wpdb->posts posts ON comments.comment_post_ID=posts.ID WHERE posts.post_type = %s", $key));
1482 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1483 $wpdb->query($wpdb->prepare("UPDATE $wpdb->posts SET comment_count = %d WHERE post_author != 0 AND post_type = %s", $comment_count, $key));
1484 }
1485
1486 $this->optimize_table($wpdb->commentmeta);
1487 $this->optimize_table($wpdb->comments);
1488
1489 $log = __('All comments have been deleted', 'disable-comments');
1490 }
1491 } elseif ($formArray['delete_mode'] == 'delete_spam') {
1492
1493 // Delete spam comments and their metadata (excluding allowed comment types)
1494 $allowed_types = $this->get_allowed_comment_types();
1495
1496 if (empty($allowed_types)) {
1497 // No allowed types, delete all spam comments
1498 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1499 $wpdb->query($wpdb->prepare("DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID WHERE comments.comment_approved = %s", 'spam'));
1500 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1501 $wpdb->query($wpdb->prepare("DELETE comments FROM $wpdb->comments comments WHERE comments.comment_approved = %s", 'spam'));
1502 } else {
1503 // Build exclusion query for allowed comment types
1504 $placeholders = implode(', ', array_fill(0, count($allowed_types), '%s'));
1505 $params = array_merge(array('spam'), $allowed_types);
1506
1507 // Delete comment metadata
1508 $query = $wpdb->prepare(
1509 "DELETE cmeta FROM $wpdb->commentmeta cmeta INNER JOIN $wpdb->comments comments ON cmeta.comment_id=comments.comment_ID WHERE comments.comment_approved = %s AND comments.comment_type NOT IN ($placeholders)",
1510 $params
1511 );
1512 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1513 $wpdb->query($query);
1514
1515 // Delete comments
1516 $query = $wpdb->prepare(
1517 "DELETE comments FROM $wpdb->comments comments WHERE comments.comment_approved = %s AND comments.comment_type NOT IN ($placeholders)",
1518 $params
1519 );
1520 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1521 $wpdb->query($query);
1522 }
1523
1524 $this->optimize_table($wpdb->commentmeta);
1525 $this->optimize_table($wpdb->comments);
1526 $log = __('All spam comments have been deleted.', 'disable-comments');
1527 }
1528 }
1529 delete_transient('wc_count_comments');
1530 return $log;
1531 }
1532
1533 private function discussion_settings_allowed() {
1534 if (defined('DISABLE_COMMENTS_ALLOW_DISCUSSION_SETTINGS') && DISABLE_COMMENTS_ALLOW_DISCUSSION_SETTINGS == true) {
1535 return true;
1536 }
1537 }
1538
1539 public function single_site_deactivate() {
1540 // for single sites, delete the options upon deactivation, not uninstall.
1541 delete_option('disable_comments_options');
1542 }
1543
1544 /**
1545 * We need fresh data in every call. Called after switching to blog in loop.
1546 *
1547 * @return int The number of comments.
1548 */
1549 protected function __get_comment_count() {
1550 global $wpdb;
1551
1552 // Exclude allowed comment types from the count since they cannot be deleted
1553 // and should not be displayed in the "Total Comments" count in the Delete Comments tab
1554 $allowed_types = $this->get_allowed_comment_types();
1555
1556 if (empty($allowed_types)) {
1557 // No allowed types, count all comments
1558 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1559 return $wpdb->get_var("SELECT COUNT(comment_id) FROM $wpdb->comments");
1560 }
1561
1562 // Build exclusion query for allowed comment types
1563 $placeholders = implode(', ', array_fill(0, count($allowed_types), '%s'));
1564 $query = $wpdb->prepare(
1565 "SELECT COUNT(comment_id) FROM $wpdb->comments WHERE comment_type NOT IN ($placeholders)",
1566 $allowed_types
1567 );
1568
1569 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1570 return $wpdb->get_var($query);
1571 }
1572
1573 /**
1574 * Optimize a given table in the WordPress database.
1575 *
1576 * @param string $table_name The name of the table to optimize.
1577 */
1578 protected function optimize_table($table_name) {
1579 global $wpdb;
1580
1581 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1582 return $wpdb->query("OPTIMIZE TABLE " . esc_sql($table_name));
1583 }
1584
1585 /**
1586 * Truncate a given table in the WordPress database.
1587 *
1588 * @param string $table_name The name of the table to truncate.
1589 */
1590 protected function truncate_table($table_name) {
1591 global $wpdb;
1592
1593 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1594 return $wpdb->query("TRUNCATE TABLE " . esc_sql($table_name));
1595 }
1596
1597 /**
1598 * Get the current site-wide comment status as a descriptive string.
1599 *
1600 * This function analyzes the current Disable Comments plugin configuration
1601 * and returns a string describing which content types have comments disabled.
1602 *
1603 * @return string The current comment status:
1604 * - 'all' if comments are disabled site-wide for all content types
1605 * - 'posts' if comments are disabled only for posts
1606 * - 'pages' if comments are disabled only for pages
1607 * - 'posts,pages' if comments are disabled for both posts and pages
1608 * - 'custom_type_name' for other specific content types
1609 * - 'multiple' if multiple specific types are disabled (not all)
1610 * - 'none' if comments are not disabled anywhere
1611 *
1612 * @since 2.5.2
1613 */
1614 public function get_current_comment_status() {
1615 try {
1616 // Handle case where plugin is not properly initialized
1617 if (empty($this->options)) {
1618 return 'none';
1619 }
1620
1621 // Check if comments are disabled everywhere
1622 if ($this->is_remove_everywhere()) {
1623 return 'all';
1624 }
1625
1626 // Get disabled post types
1627 $disabled_post_types = $this->get_disabled_post_types();
1628
1629 // If no post types are disabled, comments are enabled everywhere
1630 if (empty($disabled_post_types)) {
1631 return 'none';
1632 }
1633
1634 // Get all available post types that support comments
1635 $all_post_types = $this->get_all_post_types();
1636 $all_post_type_keys = array_keys($all_post_types);
1637
1638 // Check if all available post types are disabled
1639 if (count($disabled_post_types) >= count($all_post_type_keys)) {
1640 $missing_types = array_diff($all_post_type_keys, $disabled_post_types);
1641 if (empty($missing_types)) {
1642 return 'all';
1643 }
1644 }
1645
1646 // Handle specific common cases
1647 if (count($disabled_post_types) === 1) {
1648 $disabled_type = $disabled_post_types[0];
1649
1650 // Return the specific post type name for single disabled types
1651 switch ($disabled_type) {
1652 case 'post':
1653 return 'posts';
1654 case 'page':
1655 return 'pages';
1656 default:
1657 // For custom post types, return the post type slug
1658 return $disabled_type;
1659 }
1660 }
1661
1662 // Handle multiple specific post types
1663 if (count($disabled_post_types) === 2 &&
1664 in_array('post', $disabled_post_types) &&
1665 in_array('page', $disabled_post_types)) {
1666 return 'posts,pages';
1667 }
1668
1669 // For other combinations, return 'multiple' to indicate partial disabling
1670 return 'multiple';
1671 } catch (Exception $e) {
1672 // Error handling - return safe default
1673 if (defined('WP_DEBUG') && WP_DEBUG) {
1674 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging for WP_DEBUG mode
1675 error_log('Disable Comments: Error in get_current_comment_status() - ' . $e->getMessage());
1676 }
1677 return 'none';
1678 }
1679 }
1680
1681 /**
1682 * Get detailed comment status information including API restrictions.
1683 *
1684 * This function provides comprehensive information about comment restrictions
1685 * including post type restrictions, API-level restrictions, network settings,
1686 * role exclusions, and comment counts.
1687 *
1688 * @return array Associative array with detailed status information:
1689 * - 'status' => Main status (same as get_current_comment_status())
1690 * - 'disabled_post_types' => Array of disabled post type slugs
1691 * - 'disabled_post_type_labels' => Array of disabled post type labels
1692 * - 'remove_everywhere' => Boolean indicating global disable
1693 * - 'xmlrpc_disabled' => Boolean indicating XML-RPC comments disabled
1694 * - 'rest_api_disabled' => Boolean indicating REST API comments disabled
1695 * - 'total_post_types' => Total number of available post types
1696 * - 'is_configured' => Boolean indicating if plugin is configured
1697 * - 'total_comments' => Total number of comments in database
1698 * - 'network_active' => Boolean indicating if plugin is network activated
1699 * - 'sitewide_settings' => Site-wide settings status
1700 * - 'role_exclusion_enabled' => Boolean indicating if role exclusions are enabled
1701 * - 'excluded_roles' => Array of excluded role slugs
1702 * - 'excluded_role_labels' => Array of human-readable excluded role names
1703 *
1704 * @since 2.5.2
1705 */
1706 public function get_detailed_comment_status() {
1707 try {
1708 $status = $this->get_current_comment_status();
1709 $disabled_post_types = $this->get_disabled_post_types();
1710 $all_post_types = $this->get_all_post_types();
1711
1712 // Get human-readable labels for disabled post types
1713 $disabled_labels = array();
1714 foreach ($disabled_post_types as $post_type) {
1715 if (isset($all_post_types[$post_type])) {
1716 $disabled_labels[] = $all_post_types[$post_type]->labels->name;
1717 } else {
1718 // Fallback for custom post types not in the main list
1719 $post_type_obj = get_post_type_object($post_type);
1720 $disabled_labels[] = $post_type_obj ? $post_type_obj->labels->name : $post_type;
1721 }
1722 }
1723
1724 // Get total comments count
1725 $total_comments = $this->get_all_comments_number();
1726
1727 // Determine site-wide settings status
1728 $sitewide_settings = 'not_applicable';
1729 if ($this->networkactive) {
1730 $sitewide_settings = isset($this->options['sitewide_settings']) && $this->options['sitewide_settings'] ?
1731 'enabled' : 'disabled';
1732 }
1733
1734 // Process role-based exclusion information
1735 $role_exclusion_enabled = isset($this->options['enable_exclude_by_role']) && $this->options['enable_exclude_by_role'];
1736 $excluded_roles = isset($this->options['exclude_by_role']) ? $this->options['exclude_by_role'] : array();
1737
1738 // Get human-readable role names
1739 $excluded_role_labels = array();
1740 if ($role_exclusion_enabled && !empty($excluded_roles)) {
1741 $editable_roles = get_editable_roles();
1742
1743 foreach ($excluded_roles as $role) {
1744 if ($role === 'logged-out-users') {
1745 $excluded_role_labels[] = __('Logged out users', 'disable-comments');
1746 } elseif (isset($editable_roles[$role])) {
1747 $excluded_role_labels[] = translate_user_role($editable_roles[$role]['name']);
1748 } else {
1749 $excluded_role_labels[] = $role;
1750 }
1751 }
1752 }
1753
1754 return array(
1755 'status' => $status,
1756 'disabled_post_types' => $disabled_post_types,
1757 'disabled_post_type_labels' => $disabled_labels,
1758 'remove_everywhere' => $this->is_remove_everywhere(),
1759 'xmlrpc_disabled' => !empty($this->options['remove_xmlrpc_comments']),
1760 'rest_api_disabled' => !empty($this->options['remove_rest_API_comments']),
1761 'show_existing_comments' => !empty($this->options['show_existing_comments']),
1762 'total_post_types' => count($all_post_types),
1763 'is_configured' => $this->is_configured(),
1764 'total_comments' => $total_comments,
1765 'network_active' => $this->networkactive,
1766 'sitewide_settings' => $sitewide_settings,
1767 'role_exclusion_enabled' => $role_exclusion_enabled,
1768 'excluded_roles' => $excluded_roles,
1769 'excluded_role_labels' => $excluded_role_labels
1770 );
1771 } catch (Exception $e) {
1772 // Error handling - return safe defaults
1773 if (defined('WP_DEBUG') && WP_DEBUG) {
1774 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging for WP_DEBUG mode
1775 error_log('Disable Comments: Error in get_detailed_comment_status() - ' . $e->getMessage());
1776 }
1777 return array(
1778 'status' => 'none',
1779 'disabled_post_types' => array(),
1780 'disabled_post_type_labels' => array(),
1781 'remove_everywhere' => false,
1782 'xmlrpc_disabled' => false,
1783 'rest_api_disabled' => false,
1784 'show_existing_comments' => false,
1785 'total_post_types' => 0,
1786 'is_configured' => false,
1787 'total_comments' => 0,
1788 'network_active' => false,
1789 'sitewide_settings' => 'not_applicable',
1790 'role_exclusion_enabled' => false,
1791 'excluded_roles' => array(),
1792 'excluded_role_labels' => array()
1793 );
1794 }
1795 }
1796 /**
1797 * Add Disable Comments information to WordPress Site Health Info panel.
1798 *
1799 * This method integrates the plugin's status information into WordPress's
1800 * built-in Site Health system for easy debugging and site overview.
1801 *
1802 * @param array $debug_info The debug information array.
1803 * @return array Modified debug information array.
1804 *
1805 * @since 2.5.2
1806 */
1807 public function add_site_health_info($debug_info) {
1808 $data = $this->get_detailed_comment_status();
1809
1810 // Create the main status description
1811 $status_descriptions = array(
1812 'all' => __('Comments are disabled site-wide for all content types', 'disable-comments'),
1813 'posts' => __('Comments are disabled only for blog posts', 'disable-comments'),
1814 'pages' => __('Comments are disabled only for pages', 'disable-comments'),
1815 'posts,pages' => __('Comments are disabled for both posts and pages', 'disable-comments'),
1816 'multiple' => __('Comments are disabled for multiple specific content types', 'disable-comments'),
1817 'none' => __('Comments are enabled everywhere', 'disable-comments'),
1818 );
1819
1820 // translators: %s: disabled post types.
1821 $other_status_description = sprintf(__('Comments are disabled for: %s', 'disable-comments'), $data['status']);
1822 $status_description = isset($status_descriptions[$data['status']]) ?
1823 $status_descriptions[$data['status']] :
1824 $other_status_description;
1825
1826 // Format site-wide settings value
1827 $sitewide_settings_labels = array(
1828 'enabled' => __('Enabled', 'disable-comments'),
1829 'disabled' => __('Disabled', 'disable-comments'),
1830 'not_applicable' => __('Not applicable', 'disable-comments'),
1831 );
1832
1833 // Build the fields array using data from get_detailed_comment_status()
1834 $fields = array(
1835 'status' => array(
1836 'label' => __('Comment Status', 'disable-comments'),
1837 'value' => $status_description,
1838 ),
1839 'plugin_configured' => array(
1840 'label' => __('Plugin Configured', 'disable-comments'),
1841 'value' => $data['is_configured'] ? __('Yes', 'disable-comments') : __('No', 'disable-comments'),
1842 ),
1843 'total_comments' => array(
1844 'label' => __('Total Comments', 'disable-comments'),
1845 'value' => number_format_i18n($data['total_comments']),
1846 ),
1847 'global_disable' => array(
1848 'label' => __('Global Disable Active', 'disable-comments'),
1849 'value' => $data['remove_everywhere'] ? __('Yes', 'disable-comments') : __('No', 'disable-comments'),
1850 ),
1851 'disabled_post_type_count' => array(
1852 'label' => __('Disabled Post Types Count', 'disable-comments'),
1853 'value' => sprintf('%d of %d', count($data['disabled_post_types']), $data['total_post_types']),
1854 ),
1855 'disabled_post_types' => array(
1856 'label' => __('Disabled Post Types', 'disable-comments'),
1857 'value' => !empty($data['disabled_post_type_labels']) ?
1858 implode(', ', $data['disabled_post_type_labels']) :
1859 __('None', 'disable-comments'),
1860 ),
1861 'xmlrpc_comments' => array(
1862 'label' => __('XML-RPC Comments', 'disable-comments'),
1863 'value' => $data['xmlrpc_disabled'] ? __('Disabled', 'disable-comments') : __('Enabled', 'disable-comments'),
1864 ),
1865 'rest_api_comments' => array(
1866 'label' => __('REST API Comments', 'disable-comments'),
1867 'value' => $data['rest_api_disabled'] ? __('Disabled', 'disable-comments') : __('Enabled', 'disable-comments'),
1868 ),
1869 'show_existing_comments' => array(
1870 'label' => __('Show Existing Comments', 'disable-comments'),
1871 'value' => $data['show_existing_comments'] ? __('Yes', 'disable-comments') : __('No', 'disable-comments'),
1872 ),
1873 'network_active' => array(
1874 'label' => __('Network Active', 'disable-comments'),
1875 'value' => $data['network_active'] ? __('Yes', 'disable-comments') : __('No', 'disable-comments'),
1876 ),
1877 'sitewide_settings' => array(
1878 'label' => __('Site-wide Settings', 'disable-comments'),
1879 'value' => $sitewide_settings_labels[$data['sitewide_settings']],
1880 ),
1881 'role_exclusion_enabled' => array(
1882 'label' => __('Role-based Exclusions', 'disable-comments'),
1883 'value' => $data['role_exclusion_enabled'] ? __('Enabled', 'disable-comments') : __('Disabled', 'disable-comments'),
1884 ),
1885 'excluded_roles' => array(
1886 'label' => __('Excluded Roles', 'disable-comments'),
1887 'value' => !empty($data['excluded_role_labels']) ?
1888 implode(', ', $data['excluded_role_labels']) :
1889 __('None', 'disable-comments'),
1890 ),
1891 );
1892
1893 // Add the section to Site Health
1894 $debug_info['disable-comments'] = array(
1895 'label' => __('Disable Comments', 'disable-comments'),
1896 'description' => __('Complete overview of comment disable settings and configuration.', 'disable-comments'),
1897 'fields' => $fields,
1898 );
1899
1900 return $debug_info;
1901 }
1902 }
1903
1904 Disable_Comments::get_instance();
1905