PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.9
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 0.8.6 All 33 releases
desktop-mode / includes / games / playtime.php

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

233 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — 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 `openstation_game_playtime_pre_record` filter is the hook for
19 * stricter policies.
20 *
21 * @package OpenStation
22 */
23
24 defined( 'ABSPATH' ) || exit;
25
26 /**
27 * The user-meta key holding the per-game play-time map.
28 *
29 * The VALUE keeps its pre-rebrand spelling on purpose: it is a
30 * persisted or externally-visible identifier, so renaming it would
31 * orphan data already written by live installs (or break a live
32 * URL). The mismatch between this constant's name and its value is
33 * deliberate — it is NOT a half-finished rename.
34 */
35 define( 'OPENSTATION_GAMES_PLAYTIME_META', 'desktop_mode_game_playtime' );
36
37 /**
38 * The user-meta key holding the per-game DAILY play-time map:
39 * `game id => array( 'YYYY-MM-DD' => seconds )`. Backs the
40 * Steam-style "last two weeks" figure; days are bucketed in the
41 * site's timezone and pruned past a rolling window (see
42 * `openstation_games_playtime_history_days`). The lifetime totals
43 * in {@see OPENSTATION_GAMES_PLAYTIME_META} are authoritative and
44 * never pruned.
45 *
46 * The VALUE keeps its pre-rebrand spelling on purpose: it is a
47 * persisted or externally-visible identifier, so renaming it would
48 * orphan data already written by live installs (or break a live
49 * URL). The mismatch between this constant's name and its value is
50 * deliberate — it is NOT a half-finished rename.
51 */
52 define( 'OPENSTATION_GAMES_PLAYTIME_DAYS_META', 'desktop_mode_game_playtime_days' );
53
54 /**
55 * Today's daily-bucket key (`YYYY-MM-DD`, site timezone).
56 *
57 * @return string
58 */
59 function openstation_games_playtime_today_key() {
60 return current_datetime()->format( 'Y-m-d' );
61 }
62
63 /**
64 * Read a user's accumulated play time.
65 *
66 * @param int $user_id Player.
67 * @param string $game Optional game id. Empty returns the full map.
68 * @return int|array<string,int> Seconds for one game, or the whole
69 * `game id => seconds` map.
70 */
71 function openstation_games_get_playtime( $user_id, $game = '' ) {
72 $map = get_user_meta( (int) $user_id, OPENSTATION_GAMES_PLAYTIME_META, true );
73 if ( ! is_array( $map ) ) {
74 $map = array();
75 }
76 $clean = array();
77 foreach ( $map as $key => $seconds ) {
78 $key = sanitize_key( (string) $key );
79 if ( '' === $key ) {
80 continue;
81 }
82 $clean[ $key ] = max( 0, (int) $seconds );
83 }
84 if ( '' !== (string) $game ) {
85 $game = sanitize_key( (string) $game );
86 return isset( $clean[ $game ] ) ? $clean[ $game ] : 0;
87 }
88 return $clean;
89 }
90
91 /**
92 * Read a user's daily play-time buckets.
93 *
94 * @param int $user_id Player.
95 * @param string $game Optional game id. Empty returns the full map.
96 * @return array Day buckets (`'YYYY-MM-DD' => seconds`) for one game,
97 * or the whole `game id => buckets` map.
98 */
99 function openstation_games_get_playtime_daily( $user_id, $game = '' ) {
100 $map = get_user_meta( (int) $user_id, OPENSTATION_GAMES_PLAYTIME_DAYS_META, true );
101 if ( ! is_array( $map ) ) {
102 $map = array();
103 }
104 $clean = array();
105 foreach ( $map as $key => $days ) {
106 $key = sanitize_key( (string) $key );
107 if ( '' === $key || ! is_array( $days ) ) {
108 continue;
109 }
110 $clean_days = array();
111 foreach ( $days as $day => $seconds ) {
112 if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $day ) ) {
113 continue;
114 }
115 $clean_days[ (string) $day ] = max( 0, (int) $seconds );
116 }
117 $clean[ $key ] = $clean_days;
118 }
119 if ( '' !== (string) $game ) {
120 $game = sanitize_key( (string) $game );
121 return isset( $clean[ $game ] ) ? $clean[ $game ] : array();
122 }
123 return $clean;
124 }
125
126 /**
127 * Add seconds to a user's play-time total for a game.
128 *
129 * @param string $game Registered game id.
130 * @param int $user_id Player.
131 * @param int $seconds Seconds to add. Clamped to
132 * `[1, openstation_games_playtime_max_increment]`.
133 * @return int|WP_Error The new total for the game on success.
134 */
135 function openstation_games_add_playtime( $game, $user_id, $seconds ) {
136 $game = sanitize_key( (string) $game );
137 $user_id = (int) $user_id;
138 $seconds = (int) $seconds;
139
140 if ( ! openstation_games_is_registered( $game ) ) {
141 return new WP_Error(
142 'openstation_unknown_game',
143 __( 'Unknown game.', 'desktop-mode' ),
144 array( 'status' => 404 )
145 );
146 }
147 if ( $user_id <= 0 ) {
148 return new WP_Error(
149 'openstation_invalid_user',
150 __( 'A valid user is required to record play time.', 'desktop-mode' ),
151 array( 'status' => 400 )
152 );
153 }
154 if ( $seconds < 1 ) {
155 return new WP_Error(
156 'openstation_invalid_playtime',
157 __( 'Play time must be a positive number of seconds.', 'desktop-mode' ),
158 array( 'status' => 400 )
159 );
160 }
161
162 /**
163 * Filter the largest play-time increment accepted in one request.
164 * The framework flushes roughly once a minute, so anything far
165 * past that is either a background-throttled tab catching up or a
166 * hostile client; the clamp bounds the damage either way.
167 *
168 * @param int $max_seconds Default 900 (15 minutes).
169 * @param string $game Game id.
170 * @param int $user_id Player.
171 */
172 $max = max( 1, (int) apply_filters( 'openstation_games_playtime_max_increment', 900, $game, $user_id ) );
173 $seconds = min( $seconds, $max );
174
175 /**
176 * Short-circuit / veto filter for play-time recording. Return a
177 * `WP_Error` to reject the increment (surfaced to the client), or
178 * `null` to proceed.
179 *
180 * @param null|WP_Error $pre Null to proceed.
181 * @param string $game Game id.
182 * @param int $user_id Player.
183 * @param int $seconds Clamped increment.
184 */
185 $pre = apply_filters( 'openstation_game_playtime_pre_record', null, $game, $user_id, $seconds );
186 if ( is_wp_error( $pre ) ) {
187 return $pre;
188 }
189
190 $map = openstation_games_get_playtime( $user_id );
191 $map[ $game ] = ( isset( $map[ $game ] ) ? $map[ $game ] : 0 ) + $seconds;
192 update_user_meta( $user_id, OPENSTATION_GAMES_PLAYTIME_META, $map );
193
194 // Daily bucket (site timezone) for the recent-activity figure,
195 // pruned to a rolling window so the meta row stays bounded. The
196 // lifetime total above is the source of truth and never shrinks.
197 $today = openstation_games_playtime_today_key();
198
199 /**
200 * Filter how many days of daily play-time buckets are retained.
201 * The Games hub needs 14 for its "last two weeks" figure.
202 *
203 * @param int $days Default 30.
204 */
205 $window = max( 1, (int) apply_filters( 'openstation_games_playtime_history_days', 30 ) );
206 $cutoff = current_datetime()->modify( '-' . ( $window - 1 ) . ' days' )->format( 'Y-m-d' );
207
208 $daily = openstation_games_get_playtime_daily( $user_id );
209 $days = isset( $daily[ $game ] ) ? $daily[ $game ] : array();
210
211 $days[ $today ] = ( isset( $days[ $today ] ) ? $days[ $today ] : 0 ) + $seconds;
212 foreach ( array_keys( $days ) as $day ) {
213 // `Y-m-d` sorts lexicographically, so string compare suffices.
214 if ( $day < $cutoff ) {
215 unset( $days[ $day ] );
216 }
217 }
218 $daily[ $game ] = $days;
219 update_user_meta( $user_id, OPENSTATION_GAMES_PLAYTIME_DAYS_META, $daily );
220
221 /**
222 * Fires after a play-time increment is recorded.
223 *
224 * @param string $game Game id.
225 * @param int $user_id Player.
226 * @param int $seconds The recorded increment.
227 * @param int $total The user's new total for the game.
228 */
229 do_action( 'openstation_game_playtime_recorded', $game, $user_id, $seconds, $map[ $game ] );
230
231 return $map[ $game ];
232 }
233