Keys `ok` and `message`. */ public static function apply( string $fix_id ): array { $fix_id = trim( $fix_id ); if ( 0 === strpos( $fix_id, self::ENABLE_PREFIX ) ) { $slug = substr( $fix_id, strlen( self::ENABLE_PREFIX ) ); $modules = self::modules(); if ( isset( $modules[ $slug ] ) ) { return self::enable_module( $modules[ $slug ] ); } } switch ( $fix_id ) { case 'flush_rewrite': return self::flush_rewrite(); case 'set_permalinks': return self::set_permalinks(); case 'set_blog_public': return self::set_blog_public(); case 'enable_beacon': return self::enable_beacon(); case 'enable_full_feed': return self::enable_full_feed(); case 'renew_security_expires': return self::renew_security_expires(); case 'regenerate_indexnow_key': return self::regenerate_indexnow_key(); case 'write_static_files': return self::write_static_files(); } return [ 'ok' => false, 'message' => __( 'Unknown fix', 'acid-aeo' ), ]; } /** * Switches a module back on. * * The rewrite rules are flagged rather than flushed here. A module that was * off registered no route on this request, so flushing now would write out * a rule set that still does not contain it — and persist that as the * truth. `Plugin::maybe_flush_rewrites()` does it on the next `init`, by * which time the module has registered; the audit's own loopback requests * are separate requests that boot WordPress from scratch, so by the time * anything checks the file, the rule is there. * * @param Module $module Module to switch on. * @return array Keys `ok` and `message`. */ private static function enable_module( Module $module ): array { if ( [] === Options::update_section( $module->slug(), [ 'enabled' => true ] ) ) { return [ 'ok' => false, 'message' => sprintf( /* translators: %s: module name. */ __( '"%s" has no settings section to switch on.', 'acid-aeo' ), $module->title() ), ]; } update_option( 'acid_aeo_flush_needed', 1 ); // The audit that follows in this same request must see the module's // files and hooks; boot registered nothing for it, so register now. // The router replaces a slug's entry, and the hooks this adds are // idempotent, so a second registration is harmless. $module->register(); return self::done( sprintf( /* translators: %s: module name. */ __( '"%s" is switched on.', 'acid-aeo' ), $module->title() ) ); } /** * Rewrites the rewrite rules. * * @return array Keys `ok` and `message`. */ private static function flush_rewrite(): array { flush_rewrite_rules( false ); delete_option( 'acid_aeo_flush_needed' ); return self::done( __( 'Permalinks were flushed, so every file this plugin publishes is routed again.', 'acid-aeo' ) ); } /** * Moves a site off plain permalinks. * * Goes through `WP_Rewrite` rather than writing the option directly: the * rewrite object is already loaded and would otherwise flush the old, * structure-less rules straight back over the new ones. * * @return array Keys `ok` and `message`. */ private static function set_permalinks(): array { global $wp_rewrite; if ( is_object( $wp_rewrite ) && method_exists( $wp_rewrite, 'set_permalink_structure' ) ) { $wp_rewrite->set_permalink_structure( self::PERMALINK_STRUCTURE ); } else { update_option( 'permalink_structure', self::PERMALINK_STRUCTURE ); } update_option( 'acid_aeo_flush_needed', 1 ); flush_rewrite_rules( false ); return self::done( __( 'Permalinks are now set to the post name, so every generated file has its own address.', 'acid-aeo' ) ); } /** * Lets search engines index the site again. * * @return array Keys `ok` and `message`. */ private static function set_blog_public(): array { update_option( 'blog_public', 1 ); return self::done( __( 'Search engines may read the site again.', 'acid-aeo' ) ); } /** * Switches on the client side citation beacon. * * @return array Keys `ok` and `message`. */ private static function enable_beacon(): array { Options::update_section( 'citations', [ 'beacon' => true ] ); return self::done( __( 'The citation beacon is on, so referrals are recorded even when a page cache answers the request.', 'acid-aeo' ) ); } /** * Switches the RSS feed from excerpts to full text. * * @return array Keys `ok` and `message`. */ private static function enable_full_feed(): array { update_option( 'rss_use_excerpt', 0 ); return self::done( __( 'The RSS feed now carries the full text of each post.', 'acid-aeo' ) ); } /** * Pushes the security.txt expiry date back out into the future. * * @return array Keys `ok` and `message`. */ private static function renew_security_expires(): array { $module = Plugin::instance()->module( 'security' ); if ( ! $module instanceof SecurityTxt ) { $module = new SecurityTxt(); } $module->renew_expires(); return self::done( sprintf( /* translators: %s: expiry date. */ __( 'The security.txt expiry date is now %s.', 'acid-aeo' ), (string) Options::get( 'security.expires', '' ) ) ); } /** * Issues a new IndexNow key. * * The key file's rewrite rule matches any name of 8 to 128 lower-case letters, * digits, hyphens and underscores and the * renderer compares the requested key with the stored one, so a new key * needs no new rule. * * @return array Keys `ok` and `message`. */ private static function regenerate_indexnow_key(): array { try { $key = bin2hex( random_bytes( 16 ) ); } catch ( Exception $error ) { $key = strtolower( wp_generate_password( 32, false ) ); } if ( '' === $key ) { return [ 'ok' => false, 'message' => __( 'A new IndexNow key could not be generated on this host.', 'acid-aeo' ), ]; } Options::update_section( 'indexnow', [ 'key' => $key ] ); return self::done( __( 'A new IndexNow key was issued and its verification file moved with it.', 'acid-aeo' ) ); } /** * Writes the generated files to disk. * * @return array Keys `ok` and `message`. */ private static function write_static_files(): array { $class_name = 'AcidStudios\\AEO\\Modules\\StaticFiles'; if ( ! class_exists( $class_name ) ) { return [ 'ok' => false, 'message' => __( 'Writing the files to disk is not available on this installation.', 'acid-aeo' ), ]; } $module = new $class_name(); if ( ! method_exists( $module, 'write_all' ) ) { return [ 'ok' => false, 'message' => __( 'Writing the files to disk is not available on this installation.', 'acid-aeo' ), ]; } $module->write_all(); return self::done( __( 'The generated files were written to disk.', 'acid-aeo' ) ); } /** * Every module that can be switched on, keyed by slug. * * @return array Slug => module. */ private static function modules(): array { $modules = []; foreach ( Plugin::MODULES as $class_name ) { if ( ! class_exists( $class_name ) ) { continue; } $module = new $class_name(); if ( $module instanceof Module ) { $modules[ $module->slug() ] = $module; } } return $modules; } /** * Reports a fix as applied, and forgets everything derived from the old state. * * @param string $message Translated sentence describing what changed. * @return array Keys `ok` and `message`. */ private static function done( string $message ): array { Cache::invalidate_all(); Environment::reset(); SiteData::reset(); ContentData::reset(); Checks::reset(); Loopback::flush(); return [ 'ok' => true, 'message' => $message, ]; } }