PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.7
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 / includes / games / playtime.php

playtime.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.7, at includes/games/playtime.php

242 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — Games play-time store.
4 *
5 * Accumulates how long each user has spent playing each game. The
6 * framework's launcher measures active window time client-side (the
7 * clock pauses while the game window is minimized) and flushes
8 * increments to `POST /games/{game}/playtime`; totals live in one
9 * user-meta map — `desktop_mode_game_playtime` — keyed by game id,
10 * values in whole seconds. A second map —
11 * `desktop_mode_game_playtime_days` — buckets the same increments by
12 * site-timezone day (rolling window) so the hub can show a
13 * Steam-style "last two weeks" figure next to the lifetime total.
14 *
15 * Trust model matches scores (arcade honesty): increments are
16 * client-asserted, the server clamps each flush to a filterable cap
17 * so a hostile client can't mint years of play in one request, and
18 * the `desktop_mode_game_playtime_pre_record` filter is the hook for
19 * stricter policies.
20 *
21 * @package WPDesktopMode
22 * @since 0.9.7
23 */
24
25 defined( 'ABSPATH' ) || exit;
26
27 /**
28 * The user-meta key holding the per-game play-time map.
29 *
30 * @since 0.9.7
31 */
32 define( 'DESKTOP_MODE_GAMES_PLAYTIME_META', 'desktop_mode_game_playtime' );
33
34 /**
35 * The user-meta key holding the per-game DAILY play-time map:
36 * `game id => array( 'YYYY-MM-DD' => seconds )`. Backs the
37 * Steam-style "last two weeks" figure; days are bucketed in the
38 * site's timezone and pruned past a rolling window (see
39 * `desktop_mode_games_playtime_history_days`). The lifetime totals
40 * in {@see DESKTOP_MODE_GAMES_PLAYTIME_META} are authoritative and
41 * never pruned.
42 *
43 * @since 0.9.7
44 */
45 define( 'DESKTOP_MODE_GAMES_PLAYTIME_DAYS_META', 'desktop_mode_game_playtime_days' );
46
47 /**
48 * Today's daily-bucket key (`YYYY-MM-DD`, site timezone).
49 *
50 * @since 0.9.7
51 *
52 * @return string
53 */
54 function desktop_mode_games_playtime_today_key() {
55 return current_datetime()->format( 'Y-m-d' );
56 }
57
58 /**
59 * Read a user's accumulated play time.
60 *
61 * @since 0.9.7
62 *
63 * @param int $user_id Player.
64 * @param string $game Optional game id. Empty returns the full map.
65 * @return int|array<string,int> Seconds for one game, or the whole
66 * `game id => seconds` map.
67 */
68 function desktop_mode_games_get_playtime( $user_id, $game = '' ) {
69 $map = get_user_meta( (int) $user_id, DESKTOP_MODE_GAMES_PLAYTIME_META, true );
70 if ( ! is_array( $map ) ) {
71 $map = array();
72 }
73 $clean = array();
74 foreach ( $map as $key => $seconds ) {
75 $key = sanitize_key( (string) $key );
76 if ( '' === $key ) {
77 continue;
78 }
79 $clean[ $key ] = max( 0, (int) $seconds );
80 }
81 if ( '' !== (string) $game ) {
82 $game = sanitize_key( (string) $game );
83 return isset( $clean[ $game ] ) ? $clean[ $game ] : 0;
84 }
85 return $clean;
86 }
87
88 /**
89 * Read a user's daily play-time buckets.
90 *
91 * @since 0.9.7
92 *
93 * @param int $user_id Player.
94 * @param string $game Optional game id. Empty returns the full map.
95 * @return array Day buckets (`'YYYY-MM-DD' => seconds`) for one game,
96 * or the whole `game id => buckets` map.
97 */
98 function desktop_mode_games_get_playtime_daily( $user_id, $game = '' ) {
99 $map = get_user_meta( (int) $user_id, DESKTOP_MODE_GAMES_PLAYTIME_DAYS_META, true );
100 if ( ! is_array( $map ) ) {
101 $map = array();
102 }
103 $clean = array();
104 foreach ( $map as $key => $days ) {
105 $key = sanitize_key( (string) $key );
106 if ( '' === $key || ! is_array( $days ) ) {
107 continue;
108 }
109 $clean_days = array();
110 foreach ( $days as $day => $seconds ) {
111 if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $day ) ) {
112 continue;
113 }
114 $clean_days[ (string) $day ] = max( 0, (int) $seconds );
115 }
116 $clean[ $key ] = $clean_days;
117 }
118 if ( '' !== (string) $game ) {
119 $game = sanitize_key( (string) $game );
120 return isset( $clean[ $game ] ) ? $clean[ $game ] : array();
121 }
122 return $clean;
123 }
124
125 /**
126 * Add seconds to a user's play-time total for a game.
127 *
128 * @since 0.9.7
129 *
130 * @param string $game Registered game id.
131 * @param int $user_id Player.
132 * @param int $seconds Seconds to add. Clamped to
133 * `[1, desktop_mode_games_playtime_max_increment]`.
134 * @return int|WP_Error The new total for the game on success.
135 */
136 function desktop_mode_games_add_playtime( $game, $user_id, $seconds ) {
137 $game = sanitize_key( (string) $game );
138 $user_id = (int) $user_id;
139 $seconds = (int) $seconds;
140
141 if ( ! desktop_mode_games_is_registered( $game ) ) {
142 return new WP_Error(
143 'desktop_mode_unknown_game',
144 __( 'Unknown game.', 'desktop-mode' ),
145 array( 'status' => 404 )
146 );
147 }
148 if ( $user_id <= 0 ) {
149 return new WP_Error(
150 'desktop_mode_invalid_user',
151 __( 'A valid user is required to record play time.', 'desktop-mode' ),
152 array( 'status' => 400 )
153 );
154 }
155 if ( $seconds < 1 ) {
156 return new WP_Error(
157 'desktop_mode_invalid_playtime',
158 __( 'Play time must be a positive number of seconds.', 'desktop-mode' ),
159 array( 'status' => 400 )
160 );
161 }
162
163 /**
164 * Filter the largest play-time increment accepted in one request.
165 * The framework flushes roughly once a minute, so anything far
166 * past that is either a background-throttled tab catching up or a
167 * hostile client; the clamp bounds the damage either way.
168 *
169 * @since 0.9.7
170 *
171 * @param int $max_seconds Default 900 (15 minutes).
172 * @param string $game Game id.
173 * @param int $user_id Player.
174 */
175 $max = max( 1, (int) apply_filters( 'desktop_mode_games_playtime_max_increment', 900, $game, $user_id ) );
176 $seconds = min( $seconds, $max );
177
178 /**
179 * Short-circuit / veto filter for play-time recording. Return a
180 * `WP_Error` to reject the increment (surfaced to the client), or
181 * `null` to proceed.
182 *
183 * @since 0.9.7
184 *
185 * @param null|WP_Error $pre Null to proceed.
186 * @param string $game Game id.
187 * @param int $user_id Player.
188 * @param int $seconds Clamped increment.
189 */
190 $pre = apply_filters( 'desktop_mode_game_playtime_pre_record', null, $game, $user_id, $seconds );
191 if ( is_wp_error( $pre ) ) {
192 return $pre;
193 }
194
195 $map = desktop_mode_games_get_playtime( $user_id );
196 $map[ $game ] = ( isset( $map[ $game ] ) ? $map[ $game ] : 0 ) + $seconds;
197 update_user_meta( $user_id, DESKTOP_MODE_GAMES_PLAYTIME_META, $map );
198
199 // Daily bucket (site timezone) for the recent-activity figure,
200 // pruned to a rolling window so the meta row stays bounded. The
201 // lifetime total above is the source of truth and never shrinks.
202 $today = desktop_mode_games_playtime_today_key();
203
204 /**
205 * Filter how many days of daily play-time buckets are retained.
206 * The Games hub needs 14 for its "last two weeks" figure.
207 *
208 * @since 0.9.7
209 *
210 * @param int $days Default 30.
211 */
212 $window = max( 1, (int) apply_filters( 'desktop_mode_games_playtime_history_days', 30 ) );
213 $cutoff = current_datetime()->modify( '-' . ( $window - 1 ) . ' days' )->format( 'Y-m-d' );
214
215 $daily = desktop_mode_games_get_playtime_daily( $user_id );
216 $days = isset( $daily[ $game ] ) ? $daily[ $game ] : array();
217
218 $days[ $today ] = ( isset( $days[ $today ] ) ? $days[ $today ] : 0 ) + $seconds;
219 foreach ( array_keys( $days ) as $day ) {
220 // `Y-m-d` sorts lexicographically, so string compare suffices.
221 if ( $day < $cutoff ) {
222 unset( $days[ $day ] );
223 }
224 }
225 $daily[ $game ] = $days;
226 update_user_meta( $user_id, DESKTOP_MODE_GAMES_PLAYTIME_DAYS_META, $daily );
227
228 /**
229 * Fires after a play-time increment is recorded.
230 *
231 * @since 0.9.7
232 *
233 * @param string $game Game id.
234 * @param int $user_id Player.
235 * @param int $seconds The recorded increment.
236 * @param int $total The user's new total for the game.
237 */
238 do_action( 'desktop_mode_game_playtime_recorded', $game, $user_id, $seconds, $map[ $game ] );
239
240 return $map[ $game ];
241 }
242