settings = $settings; $this->history = $history; } /** * Whether the policy block is switched on. * * @return bool True when signals should be published. */ public function is_enabled(): bool { return ! aiblackout_is_disabled() && (bool) $this->settings->get_declaration( 'content_signals', false ); } /** * Returns the configured signals, omitting any set to "unset". * * @return array Signal name => value. */ public function get_signals(): array { $map = array( 'search' => 'signal_search', 'ai-input' => 'signal_ai_input', 'ai-train' => 'signal_ai_train', ); $signals = array(); foreach ( $map as $signal => $key ) { $value = (string) $this->settings->get_declaration( $key, Settings::SIGNAL_UNSET ); if ( Settings::SIGNAL_YES !== $value && Settings::SIGNAL_NO !== $value ) { continue; } $signals[ $signal ] = $value; } return $signals; } /** * Builds the robots.txt block. * * @return string The block, or an empty string when nothing is declared. */ public function build_block(): string { if ( ! $this->is_enabled() ) { return ''; } $signals = $this->get_signals(); if ( empty( $signals ) ) { return ''; } $pairs = array(); foreach ( $signals as $signal => $value ) { $pairs[] = $signal . '=' . $value; } $lines = array( '# As a condition of accessing this website, you agree to abide by the', '# following content signals:', '#', '# (a) If a content-signal = yes, you may collect content for the', '# corresponding use.', '# (b) If a content-signal = no, you may not collect content for the', '# corresponding use.', '# (c) If the website operator does not include a content signal for a', '# corresponding use, the website operator neither grants nor restricts', '# permission via content signal with respect to the corresponding use.', '#', '# The content signals and their meanings are:', '#', '# search: building a search index and providing search results (e.g.,', '# returning hyperlinks and short excerpts from this website\'s', '# contents). Search does not include providing AI-generated', '# search summaries.', '# ai-input: inputting content into one or more AI models (e.g., retrieval', '# augmented generation, grounding, or other real-time taking of', '# content for generative AI search answers).', '# ai-train: training or fine-tuning AI models.', '#', '# ANY RESTRICTIONS EXPRESSED VIA CONTENT SIGNALS ARE EXPRESS RESERVATIONS', '# OF RIGHTS UNDER ARTICLE 4 OF THE EUROPEAN UNION DIRECTIVE 2019/790 ON', '# COPYRIGHT AND RELATED RIGHTS IN THE DIGITAL SINGLE MARKET.', '', 'User-agent: *', self::DIRECTIVE . ': ' . implode( ', ', $pairs ), ); return implode( "\n", $lines ); } /** * Returns a plain-language description of what is currently declared. * * @return string Human-readable summary. */ public function describe(): string { $signals = $this->get_signals(); if ( ! $this->is_enabled() || empty( $signals ) ) { return __( 'No content signals are being published.', 'ai-blackout' ); } $parts = array(); if ( isset( $signals['search'] ) ) { $parts[] = Settings::SIGNAL_YES === $signals['search'] ? __( 'search engines may index this site', 'ai-blackout' ) : __( 'search engines may not index this site', 'ai-blackout' ); } if ( isset( $signals['ai-input'] ) ) { $parts[] = Settings::SIGNAL_YES === $signals['ai-input'] ? __( 'AI systems may use this content to answer questions', 'ai-blackout' ) : __( 'AI systems may not use this content to answer questions', 'ai-blackout' ); } if ( isset( $signals['ai-train'] ) ) { $parts[] = Settings::SIGNAL_YES === $signals['ai-train'] ? __( 'this content may be used to train AI models', 'ai-blackout' ) : __( 'this content may not be used to train AI models', 'ai-blackout' ); } return ucfirst( implode( ', ', $parts ) ) . '.'; } /** * Records the current declaration in the history log. * * @return void */ public function record(): void { $this->history->record( self::TYPE, $this->describe(), $this->get_signals() ); } }