plugin = $plugin; } /** * Registers hooks. */ public function register(): void { add_action( 'wp_dashboard_setup', array( $this, 'add' ) ); } /** * Adds the widget for users who can see the SEO dashboard. */ public function add(): void { if ( ! current_user_can( Capabilities::MANAGE_SETTINGS ) ) { return; } wp_add_dashboard_widget( self::ID, __( 'SEO overview', 'wpinterface-seo' ), array( $this, 'render' ) ); wp_add_inline_style( 'dashboard', self::css() ); } /** * Prints the widget. */ public function render(): void { $report = new Report( $this->plugin ); $stats = ( new Stats( $this->plugin ) )->get(); $insights = $report->insights( $stats ); $tiles = $this->tiles( $stats ); $more = count( $insights ) - self::STEPS; echo '
'; if ( array() !== $tiles ) { printf( '
', count( $tiles ) ); foreach ( $tiles as $tile ) { printf( '
%1$s%2$s%3$s%5$s
', esc_html( $tile['label'] ), esc_html( $tile['value'] ), '' !== $tile['unit'] ? '' . esc_html( $tile['unit'] ) . '' : '', '' !== $tile['tone'] ? ' is-' . esc_attr( $tile['tone'] ) : '', esc_html( $tile['note'] ) ); } echo '
'; } printf( '

%s

', esc_html__( 'Next steps', 'wpinterface-seo' ) ); if ( array() === $insights ) { printf( '

%s

', esc_html__( 'Nothing needs your attention right now.', 'wpinterface-seo' ) ); } else { echo ''; } echo '
'; printf( '', esc_url( Report::page_url( 'dashboard' ) ), esc_html__( 'Open SEO dashboard', 'wpinterface-seo' ), $more > 0 ? '' . esc_html( sprintf( /* translators: %s: number of further next steps. */ _n( '%s more step', '%s more steps', $more, 'wpinterface-seo' ), number_format_i18n( $more ) ) ) . '' : '' ); } /** * Figures shown above the next steps. * * @param array $stats Dashboard figures. * @return list */ private function tiles( array $stats ): array { $tiles = array(); $search = $stats['search'] ?? null; $content = $stats['content']; if ( is_array( $search ) ) { $change = self::change( (float) $search['totals']['clicks'], (float) $search['previous']['clicks'] ); $tiles[] = array( 'label' => __( 'Clicks, 28 days', 'wpinterface-seo' ), 'value' => number_format_i18n( $search['totals']['clicks'] ), 'unit' => '', 'note' => null === $change ? __( 'No earlier data', 'wpinterface-seo' ) : sprintf( '%+d%%', $change ), 'tone' => null === $change || 0 === $change ? '' : ( $change > 0 ? 'up' : 'down' ), ); } else { $tiles[] = array( 'label' => __( 'Indexable', 'wpinterface-seo' ), 'value' => number_format_i18n( $content['indexable'] ), 'unit' => '', 'note' => sprintf( /* translators: %s: number of published posts. */ __( 'of %s published', 'wpinterface-seo' ), number_format_i18n( $content['published'] ) ), 'tone' => '', ); } $scored = $content['published'] - $content['none']; $tiles[] = array( 'label' => __( 'Average SEO score', 'wpinterface-seo' ), 'value' => $scored > 0 ? number_format_i18n( (int) round( $content['scoreSum'] / $scored ) ) : '–', 'unit' => $scored > 0 ? '/100' : '', 'note' => $content['none'] > 0 ? sprintf( /* translators: %s: number of posts. */ __( '%s not analyzed', 'wpinterface-seo' ), number_format_i18n( $content['none'] ) ) : __( 'All analyzed', 'wpinterface-seo' ), 'tone' => '', ); /** * Filters the figures in the "SEO overview" Dashboard widget. * * @param array $tiles Tiles: label, value, unit, note, tone (''|up|down). * @param array $stats Dashboard figures. */ $tiles = (array) apply_filters( 'wpiseo_dashboard_widget_tiles', $tiles, $stats ); return array_values( array_map( static fn( array $tile ): array => array( 'label' => (string) ( $tile['label'] ?? '' ), 'value' => (string) ( $tile['value'] ?? '' ), 'unit' => (string) ( $tile['unit'] ?? '' ), 'note' => (string) ( $tile['note'] ?? '' ), 'tone' => in_array( $tile['tone'] ?? '', array( 'up', 'down' ), true ) ? (string) $tile['tone'] : '', ), array_filter( $tiles, 'is_array' ) ) ); } /** * Percentage change, or null without a previous value. * * @param float $current Current value. * @param float $previous Previous value. */ private static function change( float $current, float $previous ): ?int { return $previous > 0 ? (int) round( ( $current - $previous ) / $previous * 100 ) : null; } /** * Severity icon with an accessible name. * * @param string $severity critical|warning|notice. */ private static function icon( string $severity ): string { $icons = array( 'critical' => array( __( 'Critical', 'wpinterface-seo' ), '' ), 'warning' => array( __( 'Should fix', 'wpinterface-seo' ), '' ), 'notice' => array( __( 'Suggestion', 'wpinterface-seo' ), '' ), ); $icon = $icons[ $severity ] ?? $icons['notice']; return sprintf( '%2$s', esc_attr( $icon[0] ), $icon[1] ); } /** * Widget styles (added to the Dashboard's own stylesheet). */ private static function css(): string { return '#wpiseo_overview .inside{margin:0;padding:0}' . '.wpiseo-widget{padding:12px}' . '.wpiseo-widget__tiles{display:grid;gap:8px}' . '.wpiseo-widget__tile{display:flex;flex-direction:column;min-width:0;background:#f6f7f7;border:1px solid #f0f0f1;border-radius:2px;padding:8px 10px}' . '.wpiseo-widget__label,.wpiseo-widget__note{font-size:12px;color:#646970}' . '.wpiseo-widget__value{font-size:18px;font-weight:600;line-height:1.4;color:#1d2327}' . '.wpiseo-widget__unit{font-size:12px;font-weight:400;color:#646970}' . '.wpiseo-widget__note.is-up{color:#007017}.wpiseo-widget__note.is-down{color:#b32d2e}' . '.wpiseo-widget__heading{margin:16px 0 4px;font-size:13px;font-weight:600}' . '.wpiseo-widget__steps{margin:0}' . '.wpiseo-widget__steps li{display:flex;gap:8px;align-items:flex-start;margin:0;padding:8px 0;border-bottom:1px solid #f0f0f1}' . '.wpiseo-widget__steps li:last-child{border-bottom:0}' . '.wpiseo-widget__icon{flex:0 0 18px;margin-top:1px}' . '.wpiseo-widget__step{flex:1 1 auto}' . '.wpiseo-widget__empty{margin:4px 0 0;color:#646970}' . '.wpiseo-widget__footer{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:10px 12px;border-top:1px solid #f0f0f1;background:#f6f7f7;font-size:12px;color:#646970}' . '.wpiseo-widget__footer a{font-size:13px}'; } }