settings = $settings; $this->gate = $gate; } /** * Module id. * * @since 1.0.0 * * @return string */ public function id(): string { return 'robots'; } /** * Module name. * * @since 1.0.0 * * @return string */ public function label(): string { return __( 'robots.txt editor', 'elevenrank' ); } /** * Registers the module's hooks. * * @since 1.0.0 * * @return void */ public function boot(): void { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); if ( $this->gate->is_active() ) { add_filter( 'robots_txt', array( $this, 'filter' ), 15, 2 ); } } /** * Registers the REST route used by the editor screen. * * @since 1.0.0 * * @return void */ public function register_routes(): void { ( new Robots_Controller( $this->settings, $this ) )->register_routes(); } /** * Puts the site owner's text in place of the default file. * * @since 1.0.0 * * @param string $output The robots.txt content so far. * @param string|int $is_public Whether the site is visible to search engines (`0` when it is not). * @return string */ public function filter( $output, $is_public ): string { $output = (string) $output; // A site that asks search engines to stay away keeps WordPress's own "Disallow: /", and the // default file is the file without the owner's text. if ( $this->building_default || ! (int) $is_public ) { return $output; } $custom = (string) $this->settings->value( 'robots', 'content' ); return '' === trim( $custom ) ? $output : $custom . "\n"; } /** * The file as it would be without any text from the site owner. * * That is WordPress's default file after every other filter has had its say, so it includes * the sitemap address and anything another plugin adds. It is what "reset to default" restores. * * @since 1.0.0 * * @return string */ public function default_text(): string { $this->building_default = true; try { // This runs WordPress's own filter to learn what the file would contain; it is not a new hook. return (string) apply_filters( 'robots_txt', $this->core_default(), '1' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound } finally { $this->building_default = false; } } /** * The file WordPress writes on its own for a site that is open to search engines. * * @since 1.0.0 * * @return string */ private function core_default(): string { // Both paths come from admin_url(), so a site that moves or renames its admin folder is described correctly. $ajax = (string) wp_parse_url( admin_url( 'admin-ajax.php' ), PHP_URL_PATH ); $admin = substr( $ajax, 0, (int) strrpos( $ajax, '/' ) + 1 ); return "User-agent: *\nDisallow: {$admin}\nAllow: {$ajax}\n"; } }