403 ] ); } check_admin_referer( 'aiep_create_policy_page' ); // Re-use an existing page we previously created (option keeps the ID). $existing_id = (int) get_option( 'aiep_policy_page_id', 0 ); if ( $existing_id ) { $existing = get_post( $existing_id ); if ( $existing && 'page' === $existing->post_type && 'trash' !== $existing->post_status ) { wp_safe_redirect( get_edit_post_link( $existing_id, 'redirect' ) ); exit; } } $site_name = get_bloginfo( 'name' ); $body = self::policy_starter_template( $site_name ); $page_id = wp_insert_post( [ 'post_title' => __( 'AI Content Policy', 'ai-explainer-pro' ), 'post_status' => 'draft', 'post_type' => 'page', 'post_content' => $body, 'post_excerpt' => __( 'How this site uses AI in the content we publish — required by EU AI Act Art. 50.', 'ai-explainer-pro' ), 'comment_status' => 'closed', 'ping_status' => 'closed', ] ); if ( is_wp_error( $page_id ) ) { wp_die( esc_html( $page_id->get_error_message() ) ); } update_option( 'aiep_policy_page_id', (int) $page_id, false ); wp_safe_redirect( get_edit_post_link( $page_id, 'redirect' ) ); exit; } /** * Starter content for the AI policy page. Plain HTML, valid in * Gutenberg as a "Classic" or free-text block — the user can then * convert to Gutenberg blocks if they want. */ private static function policy_starter_template( $site_name ) { $site = esc_html( $site_name ); // Plain string concatenation — Plugin Check rejects heredoc // syntax in plugins distributed via WordPress.org, so we build // the template piece by piece. The result is byte-identical to // the heredoc we previously shipped. $tpl = "\n"; $tpl .= "
Last updated: " . $site . " — adapt the date when you publish this page.
\n"; $tpl .= "\n\n"; $tpl .= "\n" . $site . " publishes content for our readers. We are transparent about how artificial intelligence (AI) tools are involved in producing that content. This page describes our practices, in line with Article 50 of the EU AI Act (Regulation 2024/1689).
\n"; $tpl .= "\n\n"; $tpl .= "\nEvery post on " . $site . " is analysed by the AI Explainer detection engine, which combines five complementary heuristics (statistical, linguistic, structural, vocabulary, repetition) to estimate whether the text is human-authored, AI-generated, or mixed. Authors can also manually declare the status of any post they publish. The result is displayed as a badge at the top of each article.
\n"; $tpl .= "\n\n"; $tpl .= "\nEvery post — regardless of classification — is reviewed by a member of our editorial team before publication. We are accountable for what appears on this site.
\n"; $tpl .= "\n\n"; $tpl .= "\nYou can review our content classification stats at any time on our public authenticity page. The page is regenerated automatically as new content is published and scanned.
\n"; $tpl .= "\n\n"; $tpl .= "\nQuestions about our AI usage or this policy? Reach us at your-contact-email@example.com.
\n"; $tpl .= "\n"; return $tpl; } public static function handle_download_html() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized.', 'ai-explainer-pro' ), '', [ 'response' => 403 ] ); } check_admin_referer( 'aiep_compliance_report' ); $html = AI_Explainer_Pro_Compliance::generate_report_html(); $filename = 'ai-explainer-pro-compliance-report-' . gmdate( 'Y-m-d' ) . '.html'; // Discard any buffered output (PHP warnings, debug prints) so our // Content-Disposition header is the first thing the browser sees. while ( ob_get_level() ) { ob_end_clean(); } nocache_headers(); header( 'Content-Type: text/html; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . strlen( $html ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Full HTML document. echo $html; exit; } public function render() { $data = AI_Explainer_Pro_Compliance::evaluate(); $inv = $data['inventory']; $risk = $data['risk']; $never_used = ( 0 === (int) $inv['scanned'] ); ?>