';
}
$help = esc_html__( 'Standard instructions to the browser: do not second-guess the type of a file; do not let another site frame your pages (your own editor, the Customizer and payment windows are not affected, and WordPress\'s embed view of a post stays open to other sites); do not pass the full address of a page on to other sites; come back over HTTPS only; and keep USB, serial and HID devices away from every script on the page. All but the old XSS header are on from the start: switch one off if something you use needs it.', 'ai-eshop-optimizer' )
. ' ' . esc_html__( 'HTTPS only is sent only on pages that arrived over HTTPS, for six months at a time, and for this address alone: no subdomains and no preload list.', 'ai-eshop-optimizer' ) . ''
. ' ' . esc_html__( 'Applies to pages built by WordPress. Images, stylesheets and scripts are sent by the web server without asking WordPress, so they are not covered here.', 'ai-eshop-optimizer' ) . ''
. ' ' . esc_html__( 'The last one, the old XSS header, no longer does anything: the browser feature it switched on has been removed from Chrome, Edge and Safari, and Firefox never had it. It is offered because security scanners and audit checklists still report its absence, not because it protects anyone.', 'ai-eshop-optimizer' ) . '';
$row = \Rooster\Admin\Ui::field_row(
array(
'id' => 'aieo-st-headers',
'label' => __( 'Send protective response headers', 'ai-eshop-optimizer' ),
'group' => true,
'control' => $control,
'help_html' => $help,
)
);
echo '
' . $row . '
';
if ( $this->page_cache_present() ) {
echo \Rooster\Admin\Ui::notice(
esc_html__( 'A page cache is active on this site. Cached pages are sent to visitors without running PHP, so these headers do not reach them. Saving this page clears the caches it can reach, but the reliable place for these headers is your web server configuration. Checking while signed in will always show them, because a page cache is bypassed for signed-in users.', 'ai-eshop-optimizer' ),
'warning',
array( 'inline' => true )
);
}
}
private function render_readme_card( array $on ) {
$present = file_exists( ABSPATH . 'readme.html' );
$this->render_toggle(
'keep_readme_deleted',
! empty( $on['keep_readme_deleted'] ),
__( 'Remove the default readme.html', 'ai-eshop-optimizer' ),
__( 'WordPress ships a readme file in your site root that names the version. WordPress puts it back every time it updates itself, so removing it once is not enough. Modest value: the licence file next to it says much the same thing.', 'ai-eshop-optimizer' ),
$present
? __( 'The file is present.', 'ai-eshop-optimizer' )
: __( 'The file is not there.', 'ai-eshop-optimizer' ),
__( 'Keep it removed', 'ai-eshop-optimizer' )
);
}
private function render_folder_lock_toggle( array $on ) {
$state = AIEO_Security_Hardening::folder_lock_state();
$done = array_filter( $state );
if ( empty( $state ) ) {
$note = '';
} elseif ( count( $done ) === count( $state ) ) {
$note = __( 'In place in both folders.', 'ai-eshop-optimizer' );
} elseif ( empty( $done ) ) {
$note = __( 'Not in place.', 'ai-eshop-optimizer' );
} else {
$note = __( 'In place in one of the two folders only. Save again, or check that both are writable.', 'ai-eshop-optimizer' );
}
$this->render_toggle(
'lock_system_folders',
$on['lock_system_folders'],
__( 'Stop PHP running in your uploads and system folders', 'ai-eshop-optimizer' ),
__( 'If someone manages to write a file into your uploads folder, the difference between an incident and a break-in is whether the web server agrees to run it. This adds a rule to your uploads and wp-includes folders refusing to run PHP there. Your plugins folder is deliberately left alone: plugins legitimately answer at their own addresses, and blocking those breaks payments and forms.', 'ai-eshop-optimizer' ),
$note
);
}
private function render_system_folders_card() {
$probe = get_transient( self::PROBE_TRANSIENT );
list( $aieo_st_open, $aieo_st_close ) = AIEO_DMM_UI::card( array(
'anchor' => 'security-tightening-stop-php-running-in-your-content-folders',
'title' => __( 'Stop PHP running in your content folders', 'ai-eshop-optimizer' ),
'description' => __( 'If someone manages to write a file into your uploads or plugins folder, the difference between an incident and a break-in is whether the web server agrees to run it. This cannot be set from WordPress: it is a rule in your web server, and a plugin that claims otherwise is writing a file your server may never read. Below is what your server answers today, and the rule to give whoever administers it.', 'ai-eshop-optimizer' ),
'class' => 'aieo-st-card',
'col' => 2,
'footer' => '',
) );
echo $aieo_st_open;
if ( is_array( $probe ) ) {
echo '
';
$aieo_st_direct = \Rooster\Admin\Guidance::text( __( 'Answered by this server directly, not through your CDN.', 'ai-eshop-optimizer' ) );
echo $aieo_st_direct;
}
$aieo_st_give = \Rooster\Admin\Ui::notice( esc_html__( 'Give this to whoever administers the server. Before applying it, have them check the access log for any PHP file under those two folders that is being requested directly and still working, and allow those specifically. Some payment and form plugins are called at their own file.', 'ai-eshop-optimizer' ), 'warning', array( 'inline' => true ) );
echo $aieo_st_give;
$aieo_st_snippet = \Rooster\Admin\Ui::textarea(
'',
"location ~* ^/(?:wp-content|wp-includes)/.*\\.php$ {\n deny all;\n}\n# Check the access log for legitimate direct .php hits under those folders first,\n# and add an allow block for each before applying this.",
array( 'id' => 'aieo-st-snippet', 'code' => true, 'rows' => 6, 'readonly' => true, 'aria-label' => __( 'Stop PHP running in your content folders', 'ai-eshop-optimizer' ) )
);
echo $aieo_st_snippet;
echo '';
echo '';
echo $aieo_st_close;
}
private function render_notices() {
$msg = isset( $_GET['aieo_st_msg'] ) ? sanitize_key( wp_unslash( $_GET['aieo_st_msg'] ) ) : '';
if ( 'saved' === $msg ) {
echo \Rooster\Admin\Ui::notice( esc_html__( 'Settings saved.', 'ai-eshop-optimizer' ), 'success', array( 'inline' => true ) );
}
$readme = isset( $_GET['aieo_st_readme'] ) ? sanitize_key( wp_unslash( $_GET['aieo_st_readme'] ) ) : '';
$map = array(
'deleted' => array( 'success', __( 'readme.html removed.', 'ai-eshop-optimizer' ) ),
'absent' => array( 'info', __( 'readme.html was not there.', 'ai-eshop-optimizer' ) ),
'failed' => array( 'error', __( 'readme.html could not be removed. Delete it over SFTP or ask your host.', 'ai-eshop-optimizer' ) ),
'unwritable' => array( 'error', __( 'The site root is not writable, so readme.html could not be removed. Delete it over SFTP or ask your host.', 'ai-eshop-optimizer' ) ),
);
$lock = isset( $_GET['aieo_st_lock'] ) ? sanitize_key( wp_unslash( $_GET['aieo_st_lock'] ) ) : '';
if ( 'unwritable' === $lock ) {
echo \Rooster\Admin\Ui::notice( esc_html__( 'The folder rules could not be written, because one of the folders is not writable. Ask your host to allow it, or add the rules by hand.', 'ai-eshop-optimizer' ), 'destructive', array( 'inline' => true ) );
} elseif ( 'failed' === $lock ) {
echo \Rooster\Admin\Ui::notice( esc_html__( 'The folder rules could not be written. Nothing was changed.', 'ai-eshop-optimizer' ), 'destructive', array( 'inline' => true ) );
}
if ( isset( $map[ $readme ] ) ) {
$aieo_st_tone = 'error' === $map[ $readme ][0] ? 'destructive' : $map[ $readme ][0];
$aieo_st_notice = \Rooster\Admin\Ui::notice( esc_html( $map[ $readme ][1] ), $aieo_st_tone, array( 'inline' => true ) );
echo $aieo_st_notice;
}
}
}