health = $health; } /** * The meta key of the current site. User meta is shared by the sites of a network, but each site has its own checks. */ public static function meta_key(): string { global $wpdb; return $wpdb->get_blog_prefix() . self::META_SUFFIX; } /** * Hook the notice and its dismiss link. */ public function register(): void { add_action( 'admin_notices', array( $this, 'render' ) ); foreach ( array( 'consentfold_settings_saved', 'activated_plugin', 'deactivated_plugin', 'switch_theme', 'wp_create_nav_menu', 'wp_update_nav_menu', 'wp_delete_nav_menu', 'wp_update_nav_menu_item' ) as $hook ) { add_action( $hook, array( $this, 'forget' ) ); } foreach ( array( 'add_option_', 'update_option_', 'delete_option_' ) as $prefix ) { add_action( $prefix . Health::OPTION, array( $this, 'forget' ) ); } add_action( 'save_post', array( $this, 'post_changed' ) ); add_action( 'deleted_post', array( $this, 'post_changed' ) ); add_action( 'trashed_post', array( $this, 'post_changed' ) ); add_action( 'admin_post_' . self::ACTION, array( $this, 'handle_dismiss' ) ); } /** * Print the notice. */ public function render(): void { if ( ! current_user_can( Capabilities::MANAGE ) || $this->on_plugin_screen() ) { return; } $problem = $this->failure(); if ( null === $problem || $this->dismissed( get_current_user_id(), $problem['id'] ) ) { return; } $dismiss = wp_nonce_url( add_query_arg( array( 'action' => self::ACTION, 'check' => $problem['id'], ), admin_url( 'admin-post.php' ) ), self::ACTION ); printf( '

%2$s %3$s %4$s

%6$s · %8$s

', 'error', esc_html__( 'Consentfold:', 'consentfold' ), esc_html( $problem['message'] ), esc_html( $problem['fix'] ), esc_url( admin_url( 'admin.php?page=' . Menu::SLUG . '#health' ) ), esc_html__( 'Open the Health tab', 'consentfold' ), esc_url( $dismiss ), esc_html__( 'Dismiss for 7 days', 'consentfold' ) ); } /** * Forget the failure found. */ public function forget(): void { delete_transient( self::TRANSIENT ); } /** * Forget the failure found when the post is a navigation or the policy page. * * @param int $post_id Post id. */ public function post_changed( $post_id ): void { $post_id = (int) $post_id; if ( 'wp_navigation' === get_post_type( $post_id ) || ( $post_id > 0 && $this->health->policy_page_id() === $post_id ) ) { $this->forget(); } } /** * The most serious failed check. Its id is kept for an hour; the check itself is run again, so the text follows the * viewer's language and a check that has been fixed since is not shown. * * @return array{id: string, label: string, status: string, message: string, fix: string, severity: int}|null */ private function failure(): ?array { $stamp = $this->stamp(); $kept = get_transient( self::TRANSIENT ); if ( is_array( $kept ) && ( $kept['stamp'] ?? null ) === $stamp && array_key_exists( 'id', $kept ) ) { if ( null === $kept['id'] ) { return null; } $check = is_string( $kept['id'] ) ? $this->health->check( $kept['id'], false ) : null; if ( null !== $check && Health::FAIL === $check['status'] ) { return $check; } } $worst = $this->health->worst(); $problem = null !== $worst && Health::FAIL === $worst['status'] ? $worst : null; set_transient( self::TRANSIENT, array( 'id' => $problem['id'] ?? null, 'stamp' => $stamp, ), self::KEEP ); return $problem; } /** * A fingerprint of the network settings, which sites cannot hear about: a change there outdates the kept id. */ private function stamp(): string { return is_multisite() ? md5( (string) wp_json_encode( get_site_option( Repository::NETWORK_OPTION, false ) ) ) : ''; } /** * Handle the dismiss link: check the nonce and the capability, snooze, go back. */ public function handle_dismiss(): void { check_admin_referer( self::ACTION ); if ( ! current_user_can( Capabilities::MANAGE ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'consentfold' ), '', array( 'response' => 403 ) ); } $check = isset( $_GET['check'] ) ? sanitize_key( wp_unslash( $_GET['check'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by check_admin_referer() above. if ( array_key_exists( $check, Health::SEVERITY ) ) { $this->dismiss( get_current_user_id(), $check ); } wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url() ); exit; } /** * Snooze the notice of a check for a user. * * @param int $user_id User id. * @param string $check Check id. */ public function dismiss( int $user_id, string $check ): void { update_user_meta( $user_id, self::meta_key(), array( 'check' => $check, 'until' => time() + self::SNOOZE, ) ); } /** * Whether the user snoozed the notice of this check and the time has not run out. * * @param int $user_id User id. * @param string $check Check id. */ private function dismissed( int $user_id, string $check ): bool { $kept = get_user_meta( $user_id, self::meta_key(), true ); return is_array( $kept ) && ( $kept['check'] ?? null ) === $check && is_int( $kept['until'] ?? null ) && $kept['until'] > time(); } /** * Whether the screen being shown is the plugin's own. */ private function on_plugin_screen(): bool { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; return null !== $screen && 'toplevel_page_' . Menu::SLUG === $screen->id; } }