false, 'post_types' => array_values( self::available_post_types() ), 'post_statuses' => array( 'publish' ), 'scan_menus' => true, 'scan_widgets' => true, 'scan_custom_fields' => true, 'scan_excerpts' => true, 'scan_comments' => true, 'scan_comment_urls' => false, 'check_images' => true, 'check_media' => true, 'check_internal' => true, 'recheck_hours' => 72, 'timeout' => 10, 'batch_size' => 50, 'concurrency' => 4, 'time_budget' => 20, 'host_delay' => 1, 'max_redirects' => 5, 'exclude_patterns' => array(), 'ignored_hosts' => array(), 'mark_broken' => false, 'nofollow_broken' => false, 'connectivity_check' => false, ); /** * Filters the default settings. * * @param array $defaults Default settings. */ return apply_filters( 'linktender_default_settings', $defaults ); } /** * Public post types that can be scanned. * * @return string[] */ public static function available_post_types() { $types = get_post_types( array( 'public' => true ), 'names' ); unset( $types['attachment'] ); $types['wp_block'] = 'wp_block'; $types['wp_navigation'] = 'wp_navigation'; /** * Filters the post types offered for scanning. * * @param string[] $types Post type names. */ return apply_filters( 'linktender_available_post_types', $types ); } /** * Returns all settings merged with defaults. * * @return array */ public static function all() { $saved = get_option( self::OPTION, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } return wp_parse_args( $saved, self::defaults() ); } /** * Returns one setting. * * @param string $key Setting key. * @param mixed $fallback Value if missing. * @return mixed */ public static function get( $key, $fallback = null ) { $all = self::all(); return array_key_exists( $key, $all ) ? $all[ $key ] : $fallback; } /** * Updates settings (partial update). * * @param array $values Values to merge. * @return void */ public static function update( array $values ) { $all = array_merge( self::all(), $values ); update_option( self::OPTION, self::sanitize( $all ), false ); } /** * Sanitizes a full settings array. * * @param array $input Raw values. * @return array */ public static function sanitize( $input ) { $defaults = self::defaults(); $input = is_array( $input ) ? $input : array(); $out = array(); foreach ( array( 'paused', 'scan_menus', 'scan_widgets', 'scan_custom_fields', 'scan_excerpts', 'scan_comments', 'scan_comment_urls', 'check_images', 'check_media', 'check_internal', 'mark_broken', 'nofollow_broken', 'connectivity_check' ) as $bool ) { $out[ $bool ] = ! empty( $input[ $bool ] ); } $ranges = array( 'recheck_hours' => array( 6, 24 * 30 ), 'timeout' => array( 3, 30 ), 'batch_size' => array( 1, 200 ), 'concurrency' => array( 1, 10 ), 'time_budget' => array( 5, 120 ), 'host_delay' => array( 0, 30 ), 'max_redirects' => array( 0, 10 ), ); foreach ( $ranges as $key => $range ) { $value = isset( $input[ $key ] ) ? absint( $input[ $key ] ) : $defaults[ $key ]; $out[ $key ] = max( $range[0], min( $range[1], $value ) ); } $types = isset( $input['post_types'] ) ? (array) $input['post_types'] : array(); $out['post_types'] = array_values( array_intersect( array_map( 'sanitize_key', $types ), array_keys( self::available_post_types() ) ) ); $statuses = isset( $input['post_statuses'] ) ? (array) $input['post_statuses'] : array( 'publish' ); $out['post_statuses'] = array_values( array_intersect( array_map( 'sanitize_key', $statuses ), array( 'publish', 'private', 'draft', 'future', 'pending' ) ) ); if ( empty( $out['post_statuses'] ) ) { $out['post_statuses'] = array( 'publish' ); } $out['exclude_patterns'] = self::sanitize_lines( isset( $input['exclude_patterns'] ) ? $input['exclude_patterns'] : array() ); $hosts = array(); foreach ( self::sanitize_lines( isset( $input['ignored_hosts'] ) ? $input['ignored_hosts'] : array() ) as $host ) { $host = strtolower( preg_replace( '#^[a-z]+://#i', '', $host ) ); $host = preg_replace( '#[/:].*$#', '', $host ); if ( '' !== $host ) { $hosts[] = $host; } } $out['ignored_hosts'] = array_values( array_unique( $hosts ) ); /** * Filters sanitized settings before they are saved. Add-ons can keep their own keys here. * * @param array $out Sanitized settings. * @param array $input Raw input. */ return apply_filters( 'linktender_sanitize_settings', $out, $input ); } /** * Turns a textarea string or array into a clean list of lines. * * @param string|array $value Raw value. * @return string[] */ public static function sanitize_lines( $value ) { if ( ! is_array( $value ) ) { $value = preg_split( '/[\r\n]+/', (string) $value ); } $lines = array(); foreach ( $value as $line ) { $line = trim( sanitize_text_field( $line ) ); if ( '' !== $line ) { $lines[] = $line; } } return array_values( array_unique( $lines ) ); } /** * Whether a host is on the ignore list (subdomains included). * * @param string $host Host name. * @return bool */ public static function is_host_ignored( $host ) { $host = strtolower( (string) $host ); foreach ( (array) self::get( 'ignored_hosts', array() ) as $ignored ) { if ( $host === $ignored || substr( $host, -strlen( '.' . $ignored ) ) === '.' . $ignored ) { return true; } } return false; } /** * Whether a URL matches an exclusion pattern (substring or * wildcard). * * @param string $url URL. * @return bool */ public static function is_url_excluded( $url ) { foreach ( (array) self::get( 'exclude_patterns', array() ) as $pattern ) { if ( false !== strpos( $pattern, '*' ) ) { $regex = '#^' . str_replace( '\*', '.*', preg_quote( $pattern, '#' ) ) . '$#i'; if ( preg_match( $regex, $url ) ) { return true; } } elseif ( false !== stripos( $url, $pattern ) ) { return true; } } return false; } }