PluginProbe
ActivityPub / 8.2.0
ActivityPub v8.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 8.2.0, at build/stats/render.php

240 lines 8.9 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 ) ) {
69 // Fall back to direct model instantiation for blog/application actors.
70 if ( Actors::BLOG_USER_ID === $user_id ) {
71 $actor = new \Activitypub\Model\Blog();
72 } elseif ( Actors::APPLICATION_USER_ID === $user_id ) {
73 $actor = new \Activitypub\Model\Application();
74 }
75 }
76
77 $actor_webfinger = ! \is_wp_error( $actor ) ? $actor->get_webfinger() : '';
78
79 // Site name for branding.
80 $site_name = \get_bloginfo( 'name' );
81
82 $block_id = 'activitypub-stats-' . \wp_unique_id();
83 $title_text = \sprintf(
84 /* translators: %d: The year */
85 \__( 'Fediverse Stats %d', 'activitypub' ),
86 (int) $stats_year
87 );
88
89 /*
90 * Build border styles using WP_Style_Engine for sanitization.
91 * Border serialization is skipped in block.json to avoid double
92 * rendering in the editor, so we apply it here manually.
93 */
94 $border_result = \wp_style_engine_get_styles( array( 'border' => $attributes['style']['border'] ?? array() ) );
95 $extra_styles = $border_result['css'] ?? '';
96
97 // Handle preset border color slug (not part of style.border).
98 if ( ! empty( $attributes['borderColor'] ) ) {
99 $preset_color = 'var(--wp--preset--color--' . \sanitize_key( $attributes['borderColor'] ) . ')';
100 $extra_styles = 'border-color:' . $preset_color . ';' . $extra_styles;
101 }
102
103 // Resolve the border color for inner elements via CSS variable.
104 $border_color = '';
105 if ( ! empty( $attributes['style']['border']['color'] ) ) {
106 $border_color = $attributes['style']['border']['color'];
107 } elseif ( ! empty( $attributes['borderColor'] ) ) {
108 $border_color = 'var(--wp--preset--color--' . \sanitize_key( $attributes['borderColor'] ) . ')';
109 }
110
111 if ( $border_color ) {
112 $extra_styles .= '--activitypub-stats--border-color:' . \esc_attr( $border_color ) . ';';
113 }
114
115 $wrapper_attrs = array(
116 'id' => $block_id,
117 'class' => 'activitypub-stats',
118 );
119
120 $wrapper_html = \get_block_wrapper_attributes( $wrapper_attrs );
121
122 // Merge border styles into the existing style attribute.
123 if ( $extra_styles ) {
124 if ( \str_contains( $wrapper_html, 'style="' ) ) {
125 $wrapper_html = \str_replace( 'style="', 'style="' . \esc_attr( $extra_styles ), $wrapper_html );
126 } else {
127 $wrapper_html .= ' style="' . \esc_attr( $extra_styles ) . '"';
128 }
129 }
130 ?>
131 <div
132 <?php
133 // phpcs:ignore WordPress.Security.EscapeOutput
134 echo $wrapper_html;
135 ?>
136 data-year="<?php echo esc_attr( $stats_year ); ?>"
137 >
138 <div class="activitypub-stats__header">
139 <h2 class="activitypub-stats__title"><?php echo \esc_html( $title_text ); ?></h2>
140 <?php if ( $actor_webfinger ) : ?>
141 <p class="activitypub-stats__subtitle"><?php echo \esc_html( '@' . $actor_webfinger ); ?></p>
142 <?php endif; ?>
143 </div>
144
145 <div class="activitypub-stats__stats">
146 <div class="activitypub-stats__stat activitypub-stats__stat--highlight">
147 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $summary['posts_count'] ) ); ?></span>
148 <span class="activitypub-stats__stat-label"><?php esc_html_e( 'Posts Federated', 'activitypub' ); ?></span>
149 </div>
150 <div class="activitypub-stats__stat activitypub-stats__stat--highlight">
151 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $total_engagement ) ); ?></span>
152 <span class="activitypub-stats__stat-label"><?php esc_html_e( 'Total Engagements', 'activitypub' ); ?></span>
153 </div>
154 </div>
155
156 <div class="activitypub-stats__engagement">
157 <?php foreach ( $comment_types as $slug => $type_info ) : ?>
158 <?php $count = $summary[ $slug . '_count' ] ?? 0; ?>
159 <?php if ( $count > 0 ) : ?>
160 <div class="activitypub-stats__stat">
161 <span class="activitypub-stats__stat-value"><?php echo esc_html( number_format_i18n( $count ) ); ?></span>
162 <span class="activitypub-stats__stat-label"><?php echo esc_html( $type_info['label'] ); ?></span>
163 </div>
164 <?php endif; ?>
165 <?php endforeach; ?>
166 </div>
167
168 <div class="activitypub-stats__details">
169 <div class="activitypub-stats__detail">
170 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Follower Growth', 'activitypub' ); ?></span>
171 <span class="activitypub-stats__detail-value">
172 <?php echo esc_html( $change_sign . number_format_i18n( $followers_net_change ) ); ?>
173 </span>
174 <span class="activitypub-stats__detail-extra">
175 <?php
176 printf(
177 /* translators: 1: follower count at start of year, 2: follower count at end of year */
178 esc_html__( '%1$s → %2$s followers', 'activitypub' ),
179 esc_html( number_format_i18n( $followers_start ) ),
180 esc_html( number_format_i18n( $followers_end ) )
181 );
182 ?>
183 </span>
184 </div>
185
186 <?php if ( $most_active_month_name ) : ?>
187 <div class="activitypub-stats__detail">
188 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Most Active Month', 'activitypub' ); ?></span>
189 <span class="activitypub-stats__detail-value"><?php echo esc_html( $most_active_month_name ); ?></span>
190 </div>
191 <?php endif; ?>
192
193 <?php if ( ! empty( $summary['top_multiplicator'] ) ) : ?>
194 <div class="activitypub-stats__detail">
195 <span class="activitypub-stats__detail-label"><?php esc_html_e( 'Top Supporter', 'activitypub' ); ?></span>
196 <span class="activitypub-stats__detail-value">
197 <a href="<?php echo esc_url( $summary['top_multiplicator']['url'] ); ?>"><?php echo esc_html( $summary['top_multiplicator']['name'] ); ?></a>
198 </span>
199 <span class="activitypub-stats__detail-extra">
200 <?php
201 printf(
202 /* translators: %s: Number of boosts */
203 esc_html( _n( '%s boost', '%s boosts', (int) $summary['top_multiplicator']['count'], 'activitypub' ) ),
204 esc_html( number_format_i18n( $summary['top_multiplicator']['count'] ) )
205 );
206 ?>
207 </span>
208 </div>
209 <?php endif; ?>
210 </div>
211
212 <?php if ( ! empty( $summary['top_posts'] ) ) : ?>
213 <div class="activitypub-stats__top-posts">
214 <h3 class="activitypub-stats__section-title"><?php esc_html_e( 'Top Posts', 'activitypub' ); ?></h3>
215 <ol>
216 <?php foreach ( array_slice( $summary['top_posts'], 0, 5 ) as $top_post ) : ?>
217 <li>
218 <a href="<?php echo esc_url( $top_post['url'] ); ?>">
219 <?php echo esc_html( $top_post['title'] ? $top_post['title'] : __( '(no title)', 'activitypub' ) ); ?>
220 </a>
221 <span class="activitypub-stats__post-engagement">
222 <?php
223 printf(
224 /* translators: %s: engagement count */
225 esc_html__( '%s engagements', 'activitypub' ),
226 esc_html( number_format_i18n( $top_post['engagement_count'] ?? 0 ) )
227 );
228 ?>
229 </span>
230 </li>
231 <?php endforeach; ?>
232 </ol>
233 </div>
234 <?php endif; ?>
235
236 <div class="activitypub-stats__footer">
237 <span class="activitypub-stats__branding"><?php echo \esc_html( $site_name ); ?> · <?php \esc_html_e( 'Powered by the ActivityPub plugin', 'activitypub' ); ?></span>
238 </div>
239 </div>
240