get_bloginfo( 'name' ), 'agency_url' => '', 'logo_url' => '', 'brand_color' => '#2271b1', // Client report. 'report_frequency' => 'monthly', 'client_name' => '', 'report_recipients' => '', 'report_from_name' => '', 'report_from_email' => '', 'report_subject' => __( 'Maintenance report for {site_name}: {period_start} – {period_end}', 'maintained-log' ), 'report_intro' => __( "Hello {client_name},\n\nHere is a summary of the maintenance work we carried out on {site_name} during this period to keep it secure, up to date and working correctly.", 'maintained-log' ), 'report_outro' => __( "If you have any questions, just reply to this email.\n\nBest regards,\n{agency_name}", 'maintained-log' ), 'report_show_updates' => 1, 'report_show_checks' => 1, 'report_show_tasks' => 1, 'report_show_time' => 1, 'report_show_backups' => 1, 'report_show_perf' => 1, 'report_show_tech' => 1, 'report_show_vulns' => 1, 'report_hold' => 1, 'report_hold_days' => 7, // Client visibility. 'dashboard_widget' => 1, 'maintainers' => array(), 'public_report_enabled' => 0, 'report_send_if_empty' => 1, // Admin alerts. 'admin_recipients' => get_option( 'admin_email' ), 'pending_alert' => 'weekly', 'alert_on_failure' => 1, 'tech_alert' => 1, // Checks. 'checks_after_update' => 1, 'check_urls' => home_url( '/' ), 'check_timeout' => 20, 'check_slow_ms' => 3000, // Backups. 'backup_provider' => '', 'backup_frequency' => '', 'backup_retention' => 0, 'backup_reminder' => '30', // Vulnerabilities (Wordfence Intelligence). 'vuln_enabled' => 0, 'vuln_api_key' => '', 'vuln_alert' => 1, // Performance (Google PageSpeed Insights). 'perf_enabled' => 0, 'perf_api_key' => '', 'perf_urls' => home_url( '/' ), 'perf_strategies' => array( 'mobile', 'desktop' ), 'perf_frequency' => 'weekly', 'perf_alert' => 1, // Rollback. 'rollback_enabled' => 1, 'rollback_keep' => 2, // Data. 'delete_on_uninstall' => 0, ); } /** * All settings merged over defaults. * * @return array */ public static function all() { $stored = get_option( self::OPTION, array() ); return array_merge( self::defaults(), is_array( $stored ) ? $stored : array() ); } /** * One setting. * * @param string $key Setting key. * @return mixed */ public static function get( $key ) { $all = self::all(); return isset( $all[ $key ] ) ? $all[ $key ] : null; } /** * Users who maintain the site and see the maintainer summary. * * @return int[] Empty when not chosen yet. */ public static function maintainers() { return array_map( 'intval', (array) self::get( 'maintainers' ) ); } /** * Whether a user maintains the site. While nobody valid has been chosen * (or the chosen users were deleted or lost their role), every user who * can manage the plugin counts, so nobody is locked out. * * @param int $user_id User ID. * @return bool */ public static function is_maintainer( $user_id ) { if ( ! $user_id || ! user_can( $user_id, Plugin::capability() ) ) { return false; } $maintainers = array_filter( self::maintainers(), static function ( $id ) { return user_can( $id, Plugin::capability() ); } ); return ! $maintainers || in_array( (int) $user_id, $maintainers, true ); } /** * Users who could be chosen as maintainers. * * @return \WP_User[] */ public static function maintainer_candidates() { return array_values( array_filter( get_users( array( 'orderby' => 'display_name', 'number' => 100, ) ), static function ( $user ) { return user_can( $user, Plugin::capability() ); } ) ); } /** * Updates some settings, keeping the rest. * * @param array $changes Raw (unslashed) values by key. */ public static function update( array $changes ) { update_option( self::OPTION, self::sanitize( wp_slash( array_merge( self::all(), $changes ) ) ), false ); } /** * Stores defaults on first activation so they are not re-translated later. */ public static function ensure_defaults() { if ( false === get_option( self::OPTION ) ) { add_option( self::OPTION, self::defaults(), '', false ); } } /** * Frequency choices for reports. * * @return array */ public static function report_frequencies() { return array( 'never' => __( 'Only when sent manually', 'maintained-log' ), 'weekly' => __( 'Weekly', 'maintained-log' ), 'monthly' => __( 'Monthly', 'maintained-log' ), 'bimonthly' => __( 'Every two months', 'maintained-log' ), 'quarterly' => __( 'Quarterly', 'maintained-log' ), 'semiannual' => __( 'Every six months', 'maintained-log' ), ); } /** * Months covered by each automatic report, for month-based frequencies. * * @param string $frequency Report frequency. * @return int 0 when the frequency is not month-based. */ public static function report_months( $frequency ) { $months = array( 'monthly' => 1, 'bimonthly' => 2, 'quarterly' => 3, 'semiannual' => 6, ); return isset( $months[ $frequency ] ) ? $months[ $frequency ] : 0; } /** * Frequency choices for pending-update alerts. * * @return array */ public static function alert_frequencies() { return array( 'never' => __( 'Never', 'maintained-log' ), 'daily' => __( 'Daily', 'maintained-log' ), 'weekly' => __( 'Weekly', 'maintained-log' ), ); } /** * Sanitizes submitted settings. * * @param mixed $input Raw input. * @return array */ public static function sanitize( $input ) { $input = is_array( $input ) ? wp_unslash( $input ) : array(); $defaults = self::defaults(); $out = array(); $text = array( 'agency_name', 'client_name', 'report_from_name', 'report_subject', 'backup_provider' ); foreach ( $text as $key ) { $out[ $key ] = isset( $input[ $key ] ) ? sanitize_text_field( $input[ $key ] ) : $defaults[ $key ]; } $textarea = array( 'report_intro', 'report_outro' ); foreach ( $textarea as $key ) { $out[ $key ] = isset( $input[ $key ] ) ? sanitize_textarea_field( $input[ $key ] ) : $defaults[ $key ]; } $out['agency_url'] = isset( $input['agency_url'] ) ? esc_url_raw( $input['agency_url'] ) : ''; $out['logo_url'] = isset( $input['logo_url'] ) ? esc_url_raw( $input['logo_url'] ) : ''; $color = isset( $input['brand_color'] ) ? sanitize_hex_color( $input['brand_color'] ) : ''; $out['brand_color'] = $color ? $color : $defaults['brand_color']; $out['report_from_email'] = isset( $input['report_from_email'] ) ? sanitize_email( $input['report_from_email'] ) : ''; $out['report_recipients'] = self::sanitize_emails( isset( $input['report_recipients'] ) ? $input['report_recipients'] : '' ); $out['admin_recipients'] = self::sanitize_emails( isset( $input['admin_recipients'] ) ? $input['admin_recipients'] : '' ); $out['report_frequency'] = ( isset( $input['report_frequency'] ) && array_key_exists( $input['report_frequency'], self::report_frequencies() ) ) ? $input['report_frequency'] : $defaults['report_frequency']; $out['pending_alert'] = ( isset( $input['pending_alert'] ) && array_key_exists( $input['pending_alert'], self::alert_frequencies() ) ) ? $input['pending_alert'] : $defaults['pending_alert']; $checkboxes = array( 'report_show_updates', 'report_show_checks', 'report_show_tasks', 'report_show_time', 'report_show_backups', 'report_show_perf', 'report_show_tech', 'report_show_vulns', 'vuln_enabled', 'vuln_alert', 'report_hold', 'dashboard_widget', 'public_report_enabled', 'tech_alert', 'report_send_if_empty', 'perf_enabled', 'perf_alert', 'alert_on_failure', 'checks_after_update', 'rollback_enabled', 'delete_on_uninstall' ); foreach ( $checkboxes as $key ) { $out[ $key ] = empty( $input[ $key ] ) ? 0 : 1; } $out['backup_frequency'] = ( isset( $input['backup_frequency'] ) && array_key_exists( $input['backup_frequency'], Backups::frequencies() ) ) ? $input['backup_frequency'] : ''; $out['backup_reminder'] = ( isset( $input['backup_reminder'] ) && array_key_exists( (string) $input['backup_reminder'], Backups::reminder_intervals() ) ) ? (string) $input['backup_reminder'] : $defaults['backup_reminder']; $out['backup_retention'] = isset( $input['backup_retention'] ) ? min( 3650, absint( $input['backup_retention'] ) ) : 0; $out['maintainers'] = array(); foreach ( ( isset( $input['maintainers'] ) && is_array( $input['maintainers'] ) ) ? $input['maintainers'] : array() as $user_id ) { $user = get_userdata( absint( $user_id ) ); if ( $user && user_can( $user, Plugin::capability() ) ) { $out['maintainers'][] = (int) $user->ID; } } $out['maintainers'] = array_values( array_unique( $out['maintainers'] ) ); $out['report_hold_days'] = isset( $input['report_hold_days'] ) ? min( 30, max( 1, absint( $input['report_hold_days'] ) ) ) : $defaults['report_hold_days']; $out['rollback_keep'] = isset( $input['rollback_keep'] ) ? min( 5, max( 1, absint( $input['rollback_keep'] ) ) ) : $defaults['rollback_keep']; $out['vuln_api_key'] = isset( $input['vuln_api_key'] ) ? preg_replace( '/[^A-Za-z0-9_\-\.]/', '', $input['vuln_api_key'] ) : ''; $out['perf_api_key'] = isset( $input['perf_api_key'] ) ? preg_replace( '/[^A-Za-z0-9_\-]/', '', $input['perf_api_key'] ) : ''; $out['perf_urls'] = self::sanitize_url_list( isset( $input['perf_urls'] ) ? $input['perf_urls'] : '' ); $out['perf_frequency'] = ( isset( $input['perf_frequency'] ) && array_key_exists( $input['perf_frequency'], Performance::frequencies() ) ) ? $input['perf_frequency'] : $defaults['perf_frequency']; $out['perf_strategies'] = isset( $input['perf_strategies'] ) && is_array( $input['perf_strategies'] ) ? array_values( array_intersect( array_keys( Performance::strategies() ), $input['perf_strategies'] ) ) : array(); $out['check_urls'] = self::sanitize_check_urls( isset( $input['check_urls'] ) ? $input['check_urls'] : '' ); $out['check_timeout'] = isset( $input['check_timeout'] ) ? min( 60, max( 5, absint( $input['check_timeout'] ) ) ) : $defaults['check_timeout']; $out['check_slow_ms'] = isset( $input['check_slow_ms'] ) ? min( 60000, max( 500, absint( $input['check_slow_ms'] ) ) ) : $defaults['check_slow_ms']; return $out; } /** * Comma or newline separated emails, normalized to "a@b.c, d@e.f". * * @param string $raw Raw list. * @return string */ public static function sanitize_emails( $raw ) { return implode( ', ', self::parse_emails( $raw ) ); } /** * Valid addresses from a comma or newline separated list. * * @param string $raw Raw list. * @return string[] */ public static function parse_emails( $raw ) { $emails = array(); foreach ( preg_split( '/[\s,;]+/', (string) $raw ) as $candidate ) { $email = sanitize_email( $candidate ); if ( $email && is_email( $email ) ) { $emails[] = $email; } } return array_values( array_unique( $emails ) ); } /** * Keeps valid http(s) URLs, one per line. * * @param string $raw Raw textarea. * @return string */ private static function sanitize_url_list( $raw ) { $urls = array(); foreach ( preg_split( '/\r\n|\r|\n/', (string) $raw ) as $line ) { $url = esc_url_raw( trim( $line ), array( 'http', 'https' ) ); if ( $url ) { $urls[] = $url; } } return implode( "\n", array_unique( $urls ) ); } /** * Keeps one "URL | expected text" pair per line, dropping invalid URLs. * * @param string $raw Raw textarea. * @return string */ private static function sanitize_check_urls( $raw ) { $lines = array(); foreach ( preg_split( '/\r\n|\r|\n/', (string) $raw ) as $line ) { $parts = array_map( 'trim', explode( '|', $line, 2 ) ); $url = esc_url_raw( $parts[0], array( 'http', 'https' ) ); if ( ! $url ) { continue; } $expected = isset( $parts[1] ) ? sanitize_text_field( $parts[1] ) : ''; $lines[] = '' === $expected ? $url : $url . ' | ' . $expected; } return implode( "\n", $lines ); } }