<?php
/**
 * DisableComments
 *
 * This class disables the comment functionality across a WordPress site.
 * It removes comment-related UI elements from the frontend and backend,
 * unregisters widgets, disables REST API endpoints related to comments,
 * and ensures that comment feeds and related features are no longer accessible.
 *
 * @package StrategySuite
 */

namespace StrategySuite;

/**
 * Class DisableComments
 *
 * This class extends the StrategySuite\Module and handles the complete
 * disabling of comments in WordPress, affecting both frontend and backend areas.
 * It unregisters widgets, removes comment-related dashboard items, and ensures
 * comment-related REST API and XML-RPC endpoints are disabled.
 *
 * @package StrategySuite
 */
class DisableComments extends \StrategySuite\Module {

	/**
	 * Determine if the module can be registered.
	 *
	 * Always returns true for now but can be modified to be conditional in the future.
	 *
	 * @return bool True to enable the module.
	 */
	public function can_register() {
		// TODO: Create setting to turn on and off.
		return true;
	}

	/**
	 * Register hooks to disable comment functionality.
	 *
	 * This method sets up actions and filters to:
	 * - Remove comment feeds.
	 * - Disable the REST API and XML-RPC comment endpoints.
	 * - Remove comment-related dashboard widgets and admin menu items.
	 * - Prevent comments from appearing on the frontend.
	 * - Disable comment features in the Gutenberg editor.
	 *
	 * @return void
	 */
	public function register() {
		// these need to happen now
		add_action( 'widgets_init', [ $this, 'disable_rc_widget' ] );
		add_filter( 'wp_headers', [ $this, 'filter_wp_headers' ] );
		add_action( 'template_redirect', [ $this, 'filter_query' ], 9 );    // before redirect_canonical

		// Admin bar filtering has to happen here since WP 3.6
		add_action( 'template_redirect', [ $this, 'filter_admin_bar' ] );
		add_action( 'admin_init', [ $this, 'filter_admin_bar' ] );

		// these can happen later
		add_action( 'wp_loaded', [ $this, 'setup_filters' ] );

		add_action( 'enqueue_block_editor_assets', [ $this, 'filter_gutenberg_blocks' ] );
		add_filter( 'rest_endpoints', [ $this, 'filter_rest_endpoints' ] );
		add_filter( 'xmlrpc_methods', [ $this, 'disable_xmlrc_comments' ] );
		add_filter( 'rest_pre_insert_comment', [ $this, 'disable_rest_API_comments' ], 10, 2 );
		add_filter( 'comments_array', [ $this, 'filter_existing_comments' ], 20, 2 );
	}

	/**
	 * Set up filters to disable comments on both frontend and backend.
	 *
	 * Removes support for comments and trackbacks on all public post types.
	 * Also disables comment-related options and UI elements in the admin dashboard.
	 *
	 * @return void
	 */
	public function setup_filters() {
		$types = array_keys( get_post_types( array( 'public' => true ), 'objects' ) );
		if ( ! empty( $types ) ) {
			foreach ( $types as $type ) {
				// we need to know what native support was for later
				if ( post_type_supports( $type, 'comments' ) ) {
					remove_post_type_support( $type, 'comments' );
					remove_post_type_support( $type, 'trackbacks' );
				}
			}
		}

		// Filters for the admin only
		if ( is_admin() ) {
			add_action( 'admin_menu', [ $this, 'filter_admin_menu' ], 9999 );   // do this as late as possible
			add_action( 'admin_print_styles-index.php', [ $this, 'admin_css' ] );
			add_action( 'admin_print_styles-profile.php', [ $this, 'admin_css' ] );
			add_action( 'wp_dashboard_setup', [ $this, 'filter_dashboard' ] );
			add_filter( 'pre_option_default_pingback_flag', '\__return_zero' );
		} else { // Filters for front end only
			add_action( 'template_redirect', [ $this, 'check_comment_template' ] );
			add_filter( 'comments_open', [ $this, 'filter_comment_status' ], 20, 2 );
			add_filter( 'pings_open', [ $this, 'filter_comment_status' ], 20, 2 );

			// remove comments links from feed
			add_filter( 'post_comments_feed_link', '\__return_false', 10, 1 );
			add_filter( 'comments_link_feed', '\__return_false', 10, 1 );
			add_filter( 'comment_link', '\__return_false', 10, 1 );

			// remove comment count from feed
			add_filter( 'get_comments_number', '\__return_false', 10, 2 );

			// Remove feed link from header
			add_filter( 'feed_links_show_comments_feed', '\__return_false' );
		}
	}

	/**
	 * Replace the comments template with a dummy file.
	 *
	 * Ensures that no comments template is loaded for singular posts.
	 *
	 * @return void
	 */
	public function check_comment_template() {
		if ( is_singular() ) {
			// Kill the comments template. This will deal with themes that don't check comment stati properly!
			add_filter( 'comments_template', [ $this, 'dummy_comments_template' ], 20 );
			// Remove comment-reply script for themes that include it indiscriminately
			wp_deregister_script( 'comment-reply' );
			// Remove feed action
			remove_action( 'wp_head', 'feed_links_extra', 3 );
		}
	}

	/**
	 * Load a dummy comments template file.
	 *
	 * @return string Path to the dummy comments template.
	 */
	public function dummy_comments_template() {
		return STRATEGY_SUITE_INC . 'comments-template.php';
	}

	/**
	 * Remove the X-Pingback header from HTTP responses.
	 *
	 * @param array $headers The original HTTP headers.
	 * @return array Modified headers without X-Pingback.
	 */
	public function filter_wp_headers( $headers ) {
		unset( $headers['X-Pingback'] );
		return $headers;
	}

	/**
	 * Redirect any requests to comment feeds to the homepage.
	 *
	 * @return void
	 */
	public function filter_query() {
		if ( is_comment_feed() ) {
			// we are inside a comment feed
			wp_redirect( home_url() );
			exit;
		}
	}

	/**
	 * Remove the comments link from the admin bar.
	 *
	 * @return void
	 */
	public function filter_admin_bar() {
		if ( is_admin_bar_showing() ) {
			// Remove comments links from admin bar
			remove_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 );
			if ( is_multisite() ) {
				add_action( 'admin_bar_menu', [ $this, 'remove_network_comment_links' ], 500 );
			}
		}
	}

	/**
	 * Remove comment links for each site in a multisite network.
	 *
	 * @param WP_Admin_Bar $wp_admin_bar The admin bar object.
	 * @return void
	 */
	public function remove_network_comment_links( $wp_admin_bar ) {
		if ( is_user_logged_in() ) {
			foreach ( (array) $wp_admin_bar->user->blogs as $blog ) {
				$wp_admin_bar->remove_menu( 'blog-' . $blog->userblog_id . '-c' );
			}
		}
	}

	/**
	 * Remove comment-related admin menu items.
	 *
	 * Prevents access to comments pages in the admin area.
	 *
	 * @return void
	 */
	public function filter_admin_menu() {
		global $pagenow;

		if ( in_array( $pagenow, array( 'comment.php', 'edit-comments.php', 'options-discussion.php' ) ) ) {
			wp_die( esc_html__( 'Comments are closed.' ), '', array( 'response' => 403 ) );
		}

		remove_menu_page( 'edit-comments.php' );
		remove_submenu_page( 'options-general.php', 'options-discussion.php' );
	}

	/**
	 * Remove the recent comments dashboard widget.
	 *
	 * @return void
	 */
	public function filter_dashboard() {
		remove_meta_box( 'dashboard_recent_comments', 'dashboard', 'normal' );
	}

	/**
	 * Apply CSS to hide comment-related elements in the admin area.
	 *
	 * @return void
	 */
	public function admin_css() {
		echo '<style>
			#dashboard_right_now .comment-count,
			#dashboard_right_now .comment-mod-count,
			#latest-comments,
			#welcome-panel .welcome-comments,
			.user-comment-shortcuts-wrap {
				display: none !important;
			}
		</style>';
	}

	/**
	 * Ensure comments are always closed on the frontend.
	 *
	 * @param bool $open Current comment status.
	 * @param int  $post_id The post ID.
	 * @return bool False to close comments.
	 */
	public function filter_comment_status( $open, $post_id ) {
		return false;
	}

	/**
	 * Unregister the Recent Comments widget.
	 *
	 * @return void
	 */
	public function disable_rc_widget() {
		// This widget has been removed from the Dashboard in WP 3.8 and can be removed in a future version
		unregister_widget( 'WP_Widget_Recent_Comments' );
		/**
		 * The widget has added a style action when it was constructed - which will
		 * still fire even if we now unregister the widget... so filter that out
		 */
		add_filter( 'show_recent_comments_widget_style', '\__return_false' );
	}

	/**
	 * Disable the latest comments block in the Gutenberg editor.
	 *
	 * @param string $hook The current admin page hook.
	 * @return void
	 */
	public function filter_gutenberg_blocks( $hook ) {
		add_action( 'admin_footer', [ $this, 'print_footer_scripts' ] );
	}

	/**
	 * Print JavaScript to unregister Gutenberg's latest comments block.
	 *
	 * @return void
	 */
	public function print_footer_scripts() {
		?>
		<script>
			wp.domReady(() => {
				const blockType = 'core/latest-comments';
				if(wp.blocks && wp.data && wp.data.select('core/blocks').getBlockType( blockType )) {
					wp.blocks.unregisterBlockType(blockType);
				}
			});
		</script>
		<?php
	}

	/**
	 * Remove comments-related endpoints from the REST API.
	 *
	 * @param array $endpoints The current REST API endpoints.
	 * @return array Modified endpoints without comments.
	 */
	public function filter_rest_endpoints( $endpoints ) {
		if ( isset( $endpoints['comments'] ) ) {
			unset( $endpoints['comments'] );
		}
		if ( isset( $endpoints['/wp/v2/comments'] ) ) {
			unset( $endpoints['/wp/v2/comments'] );
		}
		if ( isset( $endpoints['/wp/v2/comments/(?P<id>[\d]+)'] ) ) {
			unset( $endpoints['/wp/v2/comments/(?P<id>[\d]+)'] );
		}
		return $endpoints;
	}

	/**
	 * Remove the ability to create new comments via XML-RPC.
	 *
	 * @param array $methods The available XML-RPC methods.
	 * @return array Modified methods without wp.newComment.
	 */
	public function disable_xmlrc_comments( $methods ) {
		unset( $methods['wp.newComment'] );
		return $methods;
	}

	/**
	 * Disable the insertion of comments via the REST API.
	 *
	 * @param array           $prepared_comment The prepared comment data.
	 * @param WP_REST_Request $request The REST request object.
	 * @return void
	 */
	public function disable_rest_API_comments( $prepared_comment, $request ) {
		return;
	}

	/**
	 * Prevent existing comments from being displayed.
	 *
	 * @param array $comments The current comments array.
	 * @param int   $post_id The post ID.
	 * @return array An empty array to hide comments.
	 */
	public function filter_existing_comments( $comments, $post_id ) {
		return array();
	}
}
