PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / apps / users / parts / activity-summary.php

activity-summary.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.10, at apps/users/parts/activity-summary.php

187 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /** Complete activity totals with bounded profile samples. @package OpenStation */
3 defined( 'ABSPATH' ) || exit;
4
5 /**
6 * Reduce compact site-member facts in one pass. Full WP_User objects and avatars
7 * are loaded only for the bounded lists the dashboard actually displays.
8 *
9 * @return array|WP_Error Complete snapshot or a capability/database error.
10 */
11 function openstation_users_window_activity_summary() {
12 global $wpdb;
13 if ( ! current_user_can( 'list_users' ) ) {
14 return new WP_Error( 'openstation_users_forbidden', __( 'You are not allowed to list users.', 'desktop-mode' ), array( 'status' => 403 ) );
15 }
16 $scope = is_multisite() ? $wpdb->prepare( "EXISTS (SELECT 1 FROM {$wpdb->usermeta} membership WHERE membership.user_id = u.ID AND membership.meta_key = %s)", $wpdb->get_blog_prefix() . 'capabilities' ) : '1=1';
17 // The only interpolated identifiers belong to wpdb. Scope is prepared above.
18 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Trusted identifiers and a prepared membership predicate.
19 $sql = $wpdb->prepare(
20 "SELECT u.ID, u.display_name, u.user_registered, COALESCE(l.login, 0) AS login, COALESCE(p.posts, 0) AS posts, COALESCE(p.pages, 0) AS pages, COALESCE(c.comments, 0) AS comments
21 FROM {$wpdb->users} u
22 LEFT JOIN (SELECT user_id, MAX(CAST(meta_value AS UNSIGNED)) AS login FROM {$wpdb->usermeta} WHERE meta_key = %s GROUP BY user_id) l ON l.user_id = u.ID
23 LEFT JOIN (SELECT post_author, SUM(CASE WHEN post_type = 'post' THEN 1 ELSE 0 END) AS posts, SUM(CASE WHEN post_type = 'page' THEN 1 ELSE 0 END) AS pages FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post', 'page') GROUP BY post_author) p ON p.post_author = u.ID
24 LEFT JOIN (SELECT user_id, COUNT(*) AS comments FROM {$wpdb->comments} WHERE comment_approved = '1' AND user_id > 0 GROUP BY user_id) c ON c.user_id = u.ID
25 WHERE {$scope}",
26 OPENSTATION_LAST_LOGIN_META_KEY
27 );
28 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared immediately above.
29 $rows = $wpdb->get_results( $sql, ARRAY_A );
30 if ( null === $rows || $wpdb->last_error ) {
31 return new WP_Error( 'openstation_users_activity_failed', __( 'Activity could not be loaded.', 'desktop-mode' ), array( 'status' => 500 ) );
32 }
33 $now = time();
34 $end = (int) floor( $now / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS;
35 $start = $end - 56 * DAY_IN_SECONDS;
36 $out = array(
37 'total' => count( $rows ),
38 'known' => count( $rows ),
39 'totals' => array(
40 'posts' => 0,
41 'pages' => 0,
42 'comments' => 0,
43 ),
44 'contributors' => 0,
45 'recent' => 0,
46 'active30' => 0,
47 'unrecorded' => 0,
48 'unknownPresence' => 0,
49 'onlineCount' => 0,
50 'awayCount' => 0,
51 'start' => $start,
52 'end' => $end,
53 'weeks' => array_fill( 0, 8, array( 0 ) ),
54 );
55 $samples = array_fill_keys( array( 'posts', 'pages', 'comments', 'registered', 'logins', 'online', 'away' ), array() );
56 $presence = openstation_presence_get_all();
57 foreach ( $rows as $row ) {
58 $id = (int) $row['ID'];
59 $stats = array();
60 foreach ( array( 'posts', 'pages', 'comments' ) as $kind ) {
61 $stats[ $kind ] = (int) $row[ $kind ];
62 $out['totals'][ $kind ] += $stats[ $kind ];
63 }
64 $out['contributors'] += array_sum( $stats ) > 0 ? 1 : 0;
65 $registered = strtotime( $row['user_registered'] . ' UTC' );
66 $login = (int) $row['login'];
67 $status = openstation_presence_status_from_record( $presence[ $id ] ?? array() );
68 $person = array(
69 'id' => $id,
70 'name' => $row['display_name'],
71 'registered_date' => str_replace( ' ', 'T', $row['user_registered'] ) . 'Z',
72 'openstation_last_login' => $login,
73 'openstation_presence' => $status,
74 'openstation_user_stats' => $stats,
75 );
76 foreach ( $stats as $kind => $count ) {
77 if ( $count > 0 ) {
78 openstation_users_activity_sample( $samples[ $kind ], $person, $count, 6 );
79 }
80 }
81 if ( $registered > 0 && $registered <= $now ) {
82 $out['recent'] += $registered >= $now - 30 * DAY_IN_SECONDS ? 1 : 0;
83 $bucket = (int) floor( ( $registered - $start ) / ( 7 * DAY_IN_SECONDS ) );
84 if ( $bucket >= 0 && $bucket < 8 ) {
85 ++$out['weeks'][ $bucket ][0];
86 }
87 openstation_users_activity_sample( $samples['registered'], $person, $registered, 4 );
88 }
89 if ( $login > 0 && $login <= $now ) {
90 $out['active30'] += $login >= $now - 30 * DAY_IN_SECONDS ? 1 : 0;
91 openstation_users_activity_sample( $samples['logins'], $person, $login, 6 );
92 } else {
93 ++$out['unrecorded'];
94 }
95 if ( 'online' === $status || 'inactive' === $status ) {
96 $key = 'online' === $status ? 'online' : 'away';
97 ++$out[ $key . 'Count' ];
98 openstation_users_activity_sample( $samples[ $key ], $person, 1, 6 );
99 } elseif ( 'offline' !== $status ) {
100 ++$out['unknownPresence'];
101 }
102 }
103 unset( $rows );
104 $ids = array();
105 foreach ( $samples as $list ) {
106 foreach ( $list as $person ) {
107 $ids[] = $person['id'];
108 }
109 }
110 $users = $ids ? get_users(
111 array(
112 'include' => array_unique( $ids ),
113 'number' => count( array_unique( $ids ) ),
114 'count_total' => false,
115 )
116 ) : array();
117 $profiles = array();
118 foreach ( $users as $user ) {
119 $profiles[ $user->ID ] = array(
120 'slug' => $user->user_nicename,
121 'roles' => array_values( $user->roles ),
122 'avatar_urls' => array( '48' => get_avatar_url( $user, array( 'size' => 48 ) ) ),
123 );
124 }
125 foreach ( $samples as $key => $list ) {
126 foreach ( $list as &$person ) {
127 unset( $person['_score'] );
128 $person = array_merge(
129 $person,
130 $profiles[ $person['id'] ] ?? array(
131 'slug' => '',
132 'roles' => array(),
133 )
134 );
135 }
136 unset( $person );
137 if ( in_array( $key, array( 'posts', 'pages', 'comments' ), true ) ) {
138 $out['leaders'][ $key ] = $list;
139 } else {
140 $out[ $key ] = $list;
141 }
142 }
143 /** Filter the complete totals and bounded profile samples. @param array $summary Activity snapshot. */
144 return apply_filters( 'openstation_users_window_activity_summary', $out );
145 }
146
147 /**
148 * Keep only the best bounded samples while reducing the population.
149 *
150 * @param array $list Samples, updated in place.
151 * @param array $person Compact profile.
152 * @param int $score Sort metric.
153 * @param int $limit Maximum retained profiles.
154 */
155 function openstation_users_activity_sample( array &$list, array $person, $score, $limit ) {
156 $person['_score'] = $score;
157 $list[] = $person;
158 usort(
159 $list,
160 static function ( $a, $b ) {
161 $score_order = $b['_score'] <=> $a['_score'];
162 if ( 0 !== $score_order ) {
163 return $score_order;
164 }
165 $name_order = strcmp( $a['name'], $b['name'] );
166 return 0 !== $name_order ? $name_order : ( $a['id'] <=> $b['id'] );
167 }
168 );
169 $list = array_slice( $list, 0, $limit );
170 }
171
172 /** Register the authenticated read-only activity snapshot. */
173 function openstation_users_window_register_activity_summary_route() {
174 register_rest_route(
175 'desktop-mode/v1',
176 '/users/activity-summary',
177 array(
178 'methods' => WP_REST_Server::READABLE,
179 'permission_callback' => static function () {
180 return current_user_can( 'list_users' );
181 },
182 'callback' => 'openstation_users_window_activity_summary',
183 )
184 );
185 }
186 add_action( 'rest_api_init', 'openstation_users_window_register_activity_summary_route' );
187