' . esc_html__( 'wp-login.php has its own policy, because login security plugins (reCAPTCHA, Turnstile, two-factor) load scripts there that the rest of the site does not need.', 'codo-content-security-policy' ) . '
'; } else { echo '' . esc_html__( 'Sent on every front-end page, for visitors and logged-in users alike. Not sent in wp-admin, or inside page builders and the Customizer preview.', 'codo-content-security-policy' ) . '
'; } if ( is_multisite() ) { echo '' . esc_html__( 'Network defaults are set by the network administrator and apply to every site.', 'codo-content-security-policy' ) . '
'; } $this->view->render( $context, 'site' ); $this->render_header_preview( $context ); } /** * The headers as they are sent now. * * @param string $context Context. * @return void */ private function render_header_preview( $context ) { echo '' . esc_html__( 'None.', 'codo-content-security-policy' ) . '
'; return; } $size = 0; foreach ( $headers as $name => $values ) { foreach ( $values as $value ) { $size += strlen( $name ) + strlen( $value ) + 4; echo '' . esc_html( $name ) . '' . esc_html( $value ) . '
' . esc_html( sprintf( __( 'Total size: %s.', 'codo-content-security-policy' ), size_format( $size, 1 ) ) ) . '
'; if ( $size > self::SIZE_LIMIT ) { echo '' . esc_html__( 'The headers are over 4 KB. NGINX with default FastCGI buffers can fail with "upstream sent too big header" (502). Remove sources you no longer need, or raise fastcgi_buffer_size.', 'codo-content-security-policy' ) . '
' . esc_html__( 'Browsers report what the policy blocks (or would block, in Report-Only). Identical reports are counted, not stored again; at most 500 different ones are kept, for 30 days. No IP addresses, user agents or full URLs are stored.', 'codo-content-security-policy' ) . '
'; echo '' . esc_html__( 'Anyone can send a report, so treat them as suggestions: only allow sources you recognise. Sources a scan also found are marked.', 'codo-content-security-policy' ) . '
'; if ( Settings::REPORTING_EXTERNAL === $reporting ) { /* translators: %s: URL. */ echo '' . esc_html( sprintf( __( 'Reports are sent to %s, so none are collected here.', 'codo-content-security-policy' ), (string) $this->settings->get( 'external_uri' ) ) ) . '
'; } else { echo ''; if ( $this->settings->is_collecting() ) { echo esc_html( $this->reporting_summary() ) . ' '; PolicyView::button( 'reporting', 'site', 'site', array( 'op' => 'stop' ), __( 'Stop collecting', 'codo-content-security-policy' ), 'button' ); } else { foreach ( Settings::REPORT_WINDOWS as $days ) { PolicyView::button( 'reporting', 'site', 'site', array( 'op' => 'start', 'days' => $days, ), /* translators: %d: number of days. */ sprintf( _n( 'Collect for %d day', 'Collect for %d days', $days, 'codo-content-security-policy' ), $days ), 'button' ); echo ' '; } } echo '
'; } $dropped = $this->reports->dropped(); if ( $dropped > 0 ) { echo '' . esc_html( sprintf( /* translators: %s: number of reports. */ _n( 'The report table is full: %s report about a new violation was dropped.', 'The report table is full: %s reports about new violations were dropped.', $dropped, 'codo-content-security-policy' ), number_format_i18n( $dropped ) ) ) . ' ' . esc_html__( 'Until you clear the reports, a quiet Reports tab does not mean nothing is blocked. Clear them before enforcing or promoting a draft.', 'codo-content-security-policy' ) . '
' . esc_html__( 'No reports.', 'codo-content-security-policy' ) . '
'; return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pagination only. $page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; $rows = $this->reports->page( $page, self::PER_PAGE ); $found = $this->scanned_sources(); echo '| ' . esc_html( $heading ) . ' | '; } echo '
|---|
'; for ( $i = 1; $i <= $pages; $i++ ) { if ( $i === $page ) { echo '' . esc_html( (string) $i ) . ' '; } else { echo '' . esc_html( (string) $i ) . ' '; } } echo '
'; } echo ''; PolicyView::button( 'clear_reports', 'site', 'site', array(), __( 'Clear all reports', 'codo-content-security-policy' ), 'button' ); echo '
'; } /** * One report row. * * @param array' . esc_html( (string) $row['directive'] ) . '';
if ( 'login' === $context ) {
echo ' ' . esc_html__( 'login page', 'codo-content-security-policy' ) . '';
}
echo '' . esc_html( (string) $row['blocked'] ) . '';
if ( null !== $suggestion && isset( $found[ $suggestion['directive'] . ' ' . $suggestion['source'] ] ) ) {
echo ' ' . esc_html__( 'also found by a scan', 'codo-content-security-policy' ) . '';
}
echo '' . esc_html__( 'This weakens the policy. Prefer fixing the code that needs it.', 'codo-content-security-policy' ) . '
'; } } echo '' . esc_html__( 'A scan fetches a page as a logged-out visitor and checks every script, stylesheet, font, image, frame, media file and form target it finds (including fonts and images inside its stylesheets) against the policy. Published content is scanned automatically after it is saved.', 'codo-content-security-policy' ) . '
'; echo '' . esc_html__( 'Resources added later by JavaScript (tag managers, chat widgets, ads) cannot be seen by a scan. Violation reports catch those.', 'codo-content-security-policy' ) . '
'; PolicyView::open_form(); PolicyView::hidden( 'scan', 'site', 'site', array( 'op' => 'url' ) ); echo '
';
echo ' ';
submit_button( __( 'Scan now', 'codo-content-security-policy' ), 'primary', 'submit', false );
echo '
'; if ( $this->settings->get( 'login_enabled' ) ) { PolicyView::button( 'scan', 'login', 'site', array( 'op' => 'login' ), __( 'Scan the login page', 'codo-content-security-policy' ), 'button' ); echo ' '; } PolicyView::button( 'scan', 'site', 'site', array( 'op' => 'recent' ), __( 'Scan the 20 most recently updated pages', 'codo-content-security-policy' ), 'button' ); echo '
'; $scans = $this->scheduler->url_scans(); if ( ! empty( $scans ) ) { echo '' . esc_html__( 'None found by the latest scans.', 'codo-content-security-policy' ) . '
'; } foreach ( $flagged as $post ) { $this->render_scan_result( $post['result'], '' !== $post['title'] ? $post['title'] : (string) $post['result']['url'], false ); } } /** * One scan result. * * @param array' . esc_html( (string) $result['url'] ) . '
'; if ( '' !== $result['error'] ) { echo '' . esc_html( (string) $result['error'] ) . '
' . esc_html( (string) $warning ) . '
' . esc_html( sprintf( __( 'All %d resources found are allowed.', 'codo-content-security-policy' ), (int) $result['checked'] ) ) . '
'; } $this->render_issues( (array) $result['issues'], $context, __( 'Not allowed by the live policy', 'codo-content-security-policy' ) ); $this->render_issues( (array) $result['draft'], $context, __( 'Not allowed by the draft', 'codo-content-security-policy' ) ); $this->render_import( $rows, (string) $result['url'], $context ); if ( $forget ) { echo ''; PolicyView::button( 'scan', 'site', 'site', array( 'op' => 'forget', 'url' => (string) $result['url'], ), __( 'Remove from list', 'codo-content-security-policy' ), 'button-link' ); echo '
'; } echo '' . esc_html__( 'These rules come from outside this plugin (the server, a CDN, another plugin or a meta tag). Importing adds to your policy and removes nothing. Browsers enforce separate policies together, so a merged policy can allow more than they did: check each rule is one you need.', 'codo-content-security-policy' ) . '
'; PolicyView::open_form(); PolicyView::hidden( 'import', $context, 'site', array( 'url' => $url ) ); echo '| ' . esc_html__( 'Rule', 'codo-content-security-policy' ) . ' | ' . esc_html__( 'Review', 'codo-content-security-policy' ) . ' |
|---|---|
| '; if ( in_array( $verdict, array( ImportReview::ADD, ImportReview::CONFIRM ), true ) ) { echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. } else { echo $rule; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. } echo ' | ' . esc_html( $label ) . '';
if ( '' !== $row['reason'] ) {
echo ' ' . esc_html( $row['reason'] ) . ''; } echo ' |
'; submit_button( __( 'Import ticked rules', 'codo-content-security-policy' ), 'primary', 'submit', false ); echo '
'; } echo ''; } /** * A table of scan issues with Allow buttons. * * @param array| ' . esc_html( $caption ) . ' | |||
|---|---|---|---|
' . esc_html( (string) $issue['directive'] ) . ' | ';
if ( ! empty( $issue['inline'] ) ) {
echo esc_html(
sprintf(
/* translators: %d: count. */
_n( '%d inline block', '%d inline blocks', (int) $issue['count'], 'codo-content-security-policy' ),
(int) $issue['count']
)
);
} else {
echo '' . esc_html( (string) $issue['source'] ) . '' . esc_html( (string) $issue['example'] ) . ''; } echo ' | ' . esc_html( number_format_i18n( (int) $issue['count'] ) ) . ' | '; if ( $allowed ) { echo esc_html__( 'Allowed now', 'codo-content-security-policy' ); } else { PolicyView::button( 'add_rule', $context, 'site', array( 'directive' => (string) $issue['target'], 'source' => (string) $issue['source'], 'origin' => 'scan', 'return' => 'scan', ), /* translators: 1: source, 2: directive. */ sprintf( __( 'Allow %1$s in %2$s', 'codo-content-security-policy' ), (string) $issue['source'], (string) $issue['target'] ) ); } echo ' |
| ' . esc_html__( 'Violation reports', 'codo-content-security-policy' ) . ' | ';
if ( $locked ) {
echo ' ' . esc_html__( 'Set by CODO_CSP_REPORT_URI in wp-config.php.', 'codo-content-security-policy' ) . ' '; } echo ' |
|---|
' . esc_html__( 'These constants override the settings above and show as read-only:', 'codo-content-security-policy' ) . '
'; echo 'define( \'CODO_CSP_DISABLE\', true ); // ' . esc_html__( 'Send no policy headers at all.', 'codo-content-security-policy' ) . "\n";
echo 'define( \'CODO_CSP_MODE\', \'enforce\' ); // ' . esc_html__( 'Or report-only.', 'codo-content-security-policy' ) . "\n";
echo 'define( \'CODO_CSP_REPORT_URI\', \'https://...\' ); // ' . esc_html__( 'External collector.', 'codo-content-security-policy' ) . "\n";
echo 'define( \'CODO_CSP_LOGIN\', false ); // ' . esc_html__( 'No policy on wp-login.php.', 'codo-content-security-policy' ) . '';
}
/**
* A checkbox row.
*
* @param string $key Setting key.
* @param string $label Label.
* @param string $description Description.
* @return void
*/
private function checkbox_row( $key, $label, $description ) {
$locked = $this->settings->is_locked( $key );
$id = 'codo-csp-' . $key;
echo '' . esc_html__( 'Set in wp-config.php.', 'codo-content-security-policy' ) . '
'; } echo '