PluginProbe
ActivityPub / 9.2.0
ActivityPub v9.2.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / build / stats / render.php

render.php in ActivityPub 9.2.0, at build/stats/render.php

236 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `activitypub/stats` block.
4 *
5 * @package Activitypub
6 */
7
8 use Activitypub\Blocks;
9 use Activitypub\Collection\Actors;
10 use Activitypub\Statistics;
11
12 if ( is_feed() ) {
13 return;
14 }
15
16 /* @var array $attributes Block attributes. */
17 $attributes = wp_parse_args( $attributes );
18
19 $user_id = Blocks::get_user_id( $attributes['selectedUser'] ?? 'blog' );
20 $stats_year = (int) ( $attributes['year'] ?? ( (int) \gmdate( 'Y' ) - 1 ) );
21
22 // Try stored annual summary first, fall back to live computation.
23 $summary = Statistics::get_annual_summary( $user_id, $stats_year );
24
25 if ( ! $summary ) {
26 $summary = Statistics::compile_annual_summary( $user_id, $stats_year );
27 }
28
29 if ( ! $summary || empty( $summary['posts_count'] ) ) {
30 if ( \defined( 'REST_REQUEST' ) && REST_REQUEST ) {
31 printf(
32 '<div class="components-placeholder"><div class="components-placeholder__label">%s</div><div class="components-placeholder__instructions">%s</div></div>',
33 \esc_html__( 'Fediverse Stats', 'activitypub' ),
34 \sprintf(
35 /* translators: %d: The year */
36 \esc_html__( 'No stats available for %d. Stats are collected monthly and compiled at the end of each year.', 'activitypub' ),
37 (int) $stats_year
38 )
39 );
40 }
41 return;
42 }
43
44 // Get comment types for dynamic display.
45 $comment_types = Statistics::get_comment_types_for_stats();
46
47 // Calculate total engagement.
48 $total_engagement = 0;
49 foreach ( array_keys( $comment_types ) as $ct_slug ) {
50 $total_engagement += $summary[ $ct_slug . '_count' ] ?? 0;
51 }
52
53 // Most active month name.
54 $most_active_month_name = '';
55 if ( ! empty( $summary['most_active_month'] ) ) {
56 $most_active_month_name = gmdate( 'F', gmmktime( 0, 0, 0, $summary['most_active_month'], 1, $stats_year ) );
57 }
58
59 // Follower growth.
60 $followers_start = $summary['followers_start'] ?? 0;
61 $followers_end = $summary['followers_end'] ?? 0;
62 $followers_net_change = $summary['followers_net_change'] ?? ( $followers_end - $followers_start );
63 $change_sign = $followers_net_change >= 0 ? '+' : '';
64
65 // Get actor webfinger for the card header.
66 $actor = Actors::get_by_id( $user_id );
67
68 if ( \is_wp_error( $actor ) && Actors::BLOG_USER_ID === $user_id ) {
69 // Fall back to direct model instantiation for the blog actor.
70 $actor = new \Activitypub\Model\Blog();
71 }
72
73 $actor_webfinger = ! \is_wp_error( $actor ) ? $actor->get_webfinger() : '';
74
75 // Site name for branding.
76 $site_name = \get_bloginfo( 'name' );
77
78 $block_id = 'activitypub-stats-' . \wp_unique_id();
79 $title_text = \sprintf(
80 /* translators: %d: The year */
81 \__( 'Fediverse Stats %d', 'activitypub' ),
82 (int) $stats_year
83 );
84
85 /*
86 * Build border styles using WP_Style_Engine for sanitization.
87 * Border serialization is skipped in block.json to avoid double
88 * rendering in the editor, so we apply it here manually.
89 */
90 $border_result = \wp_style_engine_get_styles( array( 'border' => $attributes['style']['border'] ?? array() ) );
91 $extra_styles = $border_result['css'] ?? '';
92
93 // Handle preset border color slug (not part of style.border).
94 if ( ! empty( $attributes['borderColor'] ) ) {
95 $preset_color = 'var(--wp--preset--color--' . \sanitize_key( $attributes['borderColor'] ) . ')';
96 $extra_styles = 'border-color:' . $preset_color . ';' . $extra_styles;
97 }
98
99 // Resolve the border color for inner elements via CSS variable.
100 $border_color = '';
101 if ( ! empty( $attributes['style']['border']['color'] ) ) {
102 $border_color = $attributes['style']['border']['color'];
103 } elseif ( ! empty( $attributes['borderColor'] ) ) {
104 $border_color = 'var(--wp--preset--color--' . \sanitize_key( $attributes['borderColor'] ) . ')';
105 }
106
107 if ( $border_color ) {
108 $extra_styles .= '--activitypub-stats--border-color:' . \esc_attr( $border_color ) . ';';
109 }
110
111 $wrapper_attrs = array(
112 'id' => $block_id,
113 'class' => 'activitypub-stats',
114 );
115
116 $wrapper_html = \get_block_wrapper_attributes( $wrapper_attrs );
117
118 // Merge border styles into the existing style attribute.
119 if ( $extra_styles ) {
120 if ( \str_contains( $wrapper_html, 'style="' ) ) {
121 $wrapper_html = \str_replace( 'style="', 'style="' . \esc_attr( $extra_styles ), $wrapper_html );
122 } else {
123 $wrapper_html .= ' style="' . \esc_attr( $extra_styles ) . '"';
124 }
125 }
126 ?>
127 <div
128 <?php
129 // phpcs:ignore WordPress.Security.EscapeOutput
130 echo $wrapper_html;
131 ?>
132 data-year="<?php echo esc_attr( $stats_year ); ?>"
133 >
134 <div class="activitypub-stats__header">
135 <h2 class="activitypub-stats__title"><?php echo \esc_html( $title_text ); ?></h2>
136 <?php if ( $actor_webfinger ) : ?>
137 <p class="activitypub-stats__subtitle"><?php echo \esc_html( '@' . $actor_webfinger ); ?></p>
138 <?php endif; ?>
139 </div>
140
141 <div class="activitypub-stats__stats">
142 <div class="activitypub-stats__stat activitypub-stats__stat--highlight">
143 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $summary['posts_count'] ) ); ?></span>
144 <span class="activitypub-stats__stat-label"><?php esc_html_e( 'Posts Federated', 'activitypub' ); ?></span>
145 </div>
146 <div class="activitypub-stats__stat activitypub-stats__stat--highlight">
147 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $total_engagement ) ); ?></span>
148 <span class="activitypub-stats__stat-label"><?php esc_html_e( 'Total Engagements', 'activitypub' ); ?></span>
149 </div>
150 </div>
151
152 <div class="activitypub-stats__engagement">
153 <?php foreach ( $comment_types as $slug => $type_info ) : ?>
154 <?php $count = $summary[ $slug . '_count' ] ?? 0; ?>
155 <?php if ( $count > 0 ) : ?>
156 <div class="activitypub-stats__stat">
157 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $count ) ); ?></span>
158 <span class="activitypub-stats__stat-label"><?php echo esc_html( $type_info['label'] ); ?></span>
159 </div>
160 <?php endif; ?>
161 <?php endforeach; ?>
162 </div>
163
164 <div class="activitypub-stats__details">
165 <div class="activitypub-stats__detail">
166 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Follower Growth', 'activitypub' ); ?></span>
167 <span class="activitypub-stats__detail-value">
168 <?php echo esc_html( $change_sign . number_format_i18n( $followers_net_change ) ); ?>
169 </span>
170 <span class="activitypub-stats__detail-extra">
171 <?php
172 printf(
173 /* translators: 1: follower count at start of year, 2: follower count at end of year */
174 esc_html__( '%1$s → %2$s followers', 'activitypub' ),
175 esc_html( number_format_i18n( $followers_start ) ),
176 esc_html( number_format_i18n( $followers_end ) )
177 );
178 ?>
179 </span>
180 </div>
181
182 <?php if ( $most_active_month_name ) : ?>
183 <div class="activitypub-stats__detail">
184 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Most Active Month', 'activitypub' ); ?></span>
185 <span class="activitypub-stats__detail-value"><?php echo esc_html( $most_active_month_name ); ?></span>
186 </div>
187 <?php endif; ?>
188
189 <?php if ( ! empty( $summary['top_multiplicator'] ) ) : ?>
190 <div class="activitypub-stats__detail">
191 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Top Supporter', 'activitypub' ); ?></span>
192 <span class="activitypub-stats__detail-value">
193 <a href="<?php echo esc_url( $summary['top_multiplicator']['url'] ); ?>"><?php echo esc_html( $summary['top_multiplicator']['name'] ); ?></a>
194 </span>
195 <span class="activitypub-stats__detail-extra">
196 <?php
197 printf(
198 /* translators: %s: Number of boosts */
199 esc_html( _n( '%s boost', '%s boosts', (int) $summary['top_multiplicator']['count'], 'activitypub' ) ),
200 esc_html( number_format_i18n( $summary['top_multiplicator']['count'] ) )
201 );
202 ?>
203 </span>
204 </div>
205 <?php endif; ?>
206 </div>
207
208 <?php if ( ! empty( $summary['top_posts'] ) ) : ?>
209 <div class="activitypub-stats__top-posts">
210 <h3 class="activitypub-stats__section-title"><?php esc_html_e( 'Top Posts', 'activitypub' ); ?></h3>
211 <ol>
212 <?php foreach ( array_slice( $summary['top_posts'], 0, 5 ) as $top_post ) : ?>
213 <li>
214 <a href="<?php echo esc_url( $top_post['url'] ); ?>">
215 <?php echo esc_html( $top_post['title'] ? $top_post['title'] : __( '(no title)', 'activitypub' ) ); ?>
216 </a>
217 <span class="activitypub-stats__post-engagement">
218 <?php
219 printf(
220 /* translators: %s: engagement count */
221 esc_html__( '%s engagements', 'activitypub' ),
222 esc_html( number_format_i18n( $top_post['engagement_count'] ?? 0 ) )
223 );
224 ?>
225 </span>
226 </li>
227 <?php endforeach; ?>
228 </ol>
229 </div>
230 <?php endif; ?>
231
232 <div class="activitypub-stats__footer">
233 <span class="activitypub-stats__branding"><?php echo \esc_html( $site_name ); ?> · <?php \esc_html_e( 'Powered by the ActivityPub plugin', 'activitypub' ); ?></span>
234 </div>
235 </div>
236