$bot_data ) { $bot_signals = array(); if ( ! empty( $bot_data['search'] ) ) { $bot_signals[] = 'search=' . $bot_data['search']; } if ( ! empty( $bot_data['ai_input'] ) ) { $bot_signals[] = 'ai-input=' . $bot_data['ai_input']; } if ( ! empty( $bot_data['ai_train'] ) ) { $bot_signals[] = 'ai-train=' . $bot_data['ai_train']; } if ( ! empty( $bot_signals ) ) { $output .= 'User-Agent: ' . $bot . "\n"; $output .= 'Content-Signal: ' . implode( ', ', $bot_signals ) . "\n"; $output .= "Allow: /\n\n"; } } // Custom bots. $custom_bots = $settings['custom_bots'] ?? array(); foreach ( $custom_bots as $bot ) { if ( empty( $bot['name'] ) ) { continue; } $custom_signals = array(); // Use specific setting if set, otherwise use global. $search_value = ! empty( $bot['search'] ) ? $bot['search'] : ( $settings['global_search'] ?? 'yes' ); $ai_input_value = ! empty( $bot['ai_input'] ) ? $bot['ai_input'] : ( $settings['global_ai_input'] ?? 'yes' ); $ai_train_value = ! empty( $bot['ai_train'] ) ? $bot['ai_train'] : ( $settings['global_ai_train'] ?? 'yes' ); if ( ! empty( $search_value ) ) { $custom_signals[] = 'search=' . $search_value; } if ( ! empty( $ai_input_value ) ) { $custom_signals[] = 'ai-input=' . $ai_input_value; } if ( ! empty( $ai_train_value ) ) { $custom_signals[] = 'ai-train=' . $ai_train_value; } if ( ! empty( $custom_signals ) ) { $output .= 'User-Agent: ' . $bot['name'] . "\n"; $output .= 'Content-Signal: ' . implode( ', ', $custom_signals ) . "\n"; $output .= "Allow: /\n\n"; } } return $output . "# END AI Content Signals\n"; } /** * Get legal text for Content Signals Policy * * @return string Legal text block as robots.txt comments. */ function ayudawp_acs_get_legal_text() { $text = "# As a condition of accessing this website, you agree to abide by the following content signals:\n"; $text .= "# (a) If a content-signal = yes, you may collect content for the corresponding use.\n"; $text .= "# (b) If a content-signal = no, you may not collect content for the corresponding use.\n"; $text .= "# (c) If the website operator does not include a content signal for a corresponding use,\n"; $text .= "# the website operator neither grants nor restricts permission via content signal\n"; $text .= "# with respect to the corresponding use.\n"; $text .= "#\n"; $text .= "# The content signals and their meanings are:\n"; $text .= "# search: building a search index and providing search results\n"; $text .= "# (e.g., returning hyperlinks and short excerpts from your website's contents).\n"; $text .= "# Search does not include providing AI-generated search summaries.\n"; $text .= "# ai-input: inputting content into one or more AI models\n"; $text .= "# (e.g., retrieval augmented generation, grounding, or other real-time\n"; $text .= "# taking of content for generative AI search answers).\n"; $text .= "# ai-train: training or fine-tuning AI models.\n"; $text .= "#\n"; $text .= "# ANY RESTRICTIONS EXPRESSED VIA CONTENT SIGNALS ARE EXPRESS RESERVATIONS OF RIGHTS\n"; $text .= "# UNDER ARTICLE 4 OF THE EUROPEAN UNION DIRECTIVE 2019/790 ON COPYRIGHT AND RELATED\n"; $text .= "# RIGHTS IN THE DIGITAL SINGLE MARKET.\n\n"; return $text; } /** * Create physical robots.txt with basic WordPress rules * * @return bool True on success, false on failure. */ function ayudawp_acs_create_physical_robots() { if ( ! ayudawp_acs_init_filesystem() ) { return false; } global $wp_filesystem; $robots_file = ABSPATH . 'robots.txt'; // Don't create if already exists. if ( $wp_filesystem->exists( $robots_file ) ) { return false; } // Check if root directory is writable. if ( ! $wp_filesystem->is_writable( ABSPATH ) ) { return false; } // Basic WordPress robots.txt content. $content = "# Basic WordPress robots.txt\n"; $content .= "# Created by AI Content Signals\n\n"; $content .= "User-agent: *\n"; $content .= "Disallow: /wp-admin/\n"; $content .= "Allow: /wp-admin/admin-ajax.php\n"; $content .= "Disallow: /wp-includes/\n"; $content .= "Disallow: /wp-content/plugins/\n"; $content .= "Disallow: /wp-content/themes/\n"; $content .= "Disallow: /wp-content/cache/\n"; $content .= "Disallow: /readme.html\n"; $content .= "Disallow: /license.txt\n\n"; // Add sitemap if available. $sitemap_url = get_sitemap_url( 'index' ); if ( $sitemap_url ) { $content .= 'Sitemap: ' . esc_url( $sitemap_url ) . "\n"; } // Write to file. return $wp_filesystem->put_contents( $robots_file, $content, FS_CHMOD_FILE ); } /** * Update physical robots.txt file if it exists * * @param array|null $settings Optional settings array; fetched from DB if null. * @return bool True on success, false on failure. */ function ayudawp_acs_update_physical_robots( $settings = null ) { if ( ! ayudawp_acs_init_filesystem() ) { return false; } global $wp_filesystem; $robots_file = ABSPATH . 'robots.txt'; // If settings not provided, get from database. if ( null === $settings ) { $settings = get_option( 'ayudawp_acs_settings', array() ); } // Create physical robots.txt if option is enabled and file doesn't exist. if ( ! $wp_filesystem->exists( $robots_file ) ) { if ( ( $settings['create_physical_robots'] ?? 'no' ) === 'yes' ) { ayudawp_acs_create_physical_robots(); } else { return false; } } if ( ! $wp_filesystem->exists( $robots_file ) || ! $wp_filesystem->is_writable( $robots_file ) ) { return false; } // Read current content. $current_content = $wp_filesystem->get_contents( $robots_file ); if ( false === $current_content ) { return false; } // Remove ALL Content Signals blocks using BEGIN/END markers. $pattern = '/\n*# BEGIN AI Content Signals\n.*?# END AI Content Signals\n*/s'; $current_content = preg_replace( $pattern, '', $current_content ); // Trim any extra newlines. $current_content = rtrim( $current_content ); // Generate new Content Signals. $content_signals = ayudawp_acs_generate_content_signals(); // Combine content. $new_content = $current_content . "\n" . $content_signals; // Write to file. return $wp_filesystem->put_contents( $robots_file, $new_content, FS_CHMOD_FILE ); } /** * Hook to update robots.txt after settings are saved * * @param array $old_value Previous settings. * @param array $new_value New settings. */ function ayudawp_acs_update_after_save( $old_value, $new_value ) { ayudawp_acs_update_physical_robots( $new_value ); } add_action( 'update_option_ayudawp_acs_settings', 'ayudawp_acs_update_after_save', 10, 2 ); /** * Hook to update robots.txt after settings are added for the first time * * @param string $option Option name. * @param mixed $value Option value. */ function ayudawp_acs_update_after_add( $option, $value ) { ayudawp_acs_update_physical_robots( $value ); } add_action( 'add_option_ayudawp_acs_settings', 'ayudawp_acs_update_after_add', 10, 2 ); /** * Remove Content Signals from physical robots.txt * * Called from uninstall.php during plugin removal. * * @return bool True on success, false on failure. */ function ayudawp_acs_remove_from_physical_robots() { if ( ! ayudawp_acs_init_filesystem() ) { return false; } global $wp_filesystem; $robots_file = ABSPATH . 'robots.txt'; if ( ! $wp_filesystem->exists( $robots_file ) || ! $wp_filesystem->is_writable( $robots_file ) ) { return false; } // Read current content. $current_content = $wp_filesystem->get_contents( $robots_file ); if ( false === $current_content ) { return false; } // Remove ALL Content Signals blocks using BEGIN/END markers. $pattern = '/\n*# BEGIN AI Content Signals\n.*?# END AI Content Signals\n*/s'; $new_content = preg_replace( $pattern, '', $current_content ); // Trim any extra newlines. $new_content = rtrim( $new_content ); // Write to file. return $wp_filesystem->put_contents( $robots_file, $new_content, FS_CHMOD_FILE ); }