PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 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 All 36 releases
desktop-mode / apps / users / parts / activity-summary.php

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

188 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 array( 'name' => openstation_plain_text_title( $person['name'] ) ),
131 $profiles[ $person['id'] ] ?? array(
132 'slug' => '',
133 'roles' => array(),
134 )
135 );
136 }
137 unset( $person );
138 if ( in_array( $key, array( 'posts', 'pages', 'comments' ), true ) ) {
139 $out['leaders'][ $key ] = $list;
140 } else {
141 $out[ $key ] = $list;
142 }
143 }
144 /** Filter the complete totals and bounded profile samples. @param array $summary Activity snapshot. */
145 return apply_filters( 'openstation_users_window_activity_summary', $out );
146 }
147
148 /**
149 * Keep only the best bounded samples while reducing the population.
150 *
151 * @param array $list Samples, updated in place.
152 * @param array $person Compact profile.
153 * @param int $score Sort metric.
154 * @param int $limit Maximum retained profiles.
155 */
156 function openstation_users_activity_sample( array &$list, array $person, $score, $limit ) {
157 $person['_score'] = $score;
158 $list[] = $person;
159 usort(
160 $list,
161 static function ( $a, $b ) {
162 $score_order = $b['_score'] <=> $a['_score'];
163 if ( 0 !== $score_order ) {
164 return $score_order;
165 }
166 $name_order = strcmp( $a['name'], $b['name'] );
167 return 0 !== $name_order ? $name_order : ( $a['id'] <=> $b['id'] );
168 }
169 );
170 $list = array_slice( $list, 0, $limit );
171 }
172
173 /** Register the authenticated read-only activity snapshot. */
174 function openstation_users_window_register_activity_summary_route() {
175 register_rest_route(
176 'desktop-mode/v1',
177 '/users/activity-summary',
178 array(
179 'methods' => WP_REST_Server::READABLE,
180 'permission_callback' => static function () {
181 return current_user_can( 'list_users' );
182 },
183 'callback' => 'openstation_users_window_activity_summary',
184 )
185 );
186 }
187 add_action( 'rest_api_init', 'openstation_users_window_register_activity_summary_route' );
188