PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.5
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.5
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 / living-tree / helpers.php

helpers.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.5, at includes/living-tree/helpers.php

378 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — Living Tree: metric helpers.
4 *
5 * The scalar signals the snapshot builder folds into the site's DNA.
6 * Each helper composes existing WordPress aggregates (`wp_count_posts`,
7 * `wp_count_comments`, `wp_count_terms`), the site-views traffic signal,
8 * and framework presence — never a per-row payload. The golden rule
9 * (WordPress emits hormones, never geometry) starts here: everything
10 * returned is a scalar or a tiny capped list.
11 *
12 * @package WPDesktopMode
13 * @since 0.9.4
14 */
15
16 defined( 'ABSPATH' ) || exit;
17
18 /**
19 * The site's inception moment as a unix timestamp — the stable half of
20 * the determinism seed (`siteUrl|installEpoch`), so it must never drift
21 * between requests. Composed (core has no first-party "install time"):
22 * the earlier of the oldest user registration and the oldest published
23 * post date.
24 *
25 * @since 0.9.4
26 *
27 * @return int Unix timestamp, or 0 when the site has neither.
28 */
29 function desktop_mode_living_tree_install_epoch() {
30 global $wpdb;
31
32 $oldest_user = $wpdb->get_var(
33 "SELECT MIN( user_registered ) FROM {$wpdb->users}"
34 );
35 $oldest_post = $wpdb->get_var(
36 "SELECT MIN( post_date_gmt ) FROM {$wpdb->posts}
37 WHERE post_status = 'publish' AND post_date_gmt > '1970-01-01 00:00:01'"
38 );
39
40 $candidates = array();
41 foreach ( array( $oldest_user, $oldest_post ) as $mysql_date ) {
42 if ( $mysql_date ) {
43 $ts = strtotime( $mysql_date . ' UTC' );
44 if ( $ts > 0 ) {
45 $candidates[] = $ts;
46 }
47 }
48 }
49
50 return empty( $candidates ) ? 0 : min( $candidates );
51 }
52
53 /**
54 * Age of the site in whole days, from the install epoch. Clamped to be
55 * non-negative — the master clock never runs backwards.
56 *
57 * @since 0.9.4
58 *
59 * @return int Whole days since the site's inception. >= 0.
60 */
61 function desktop_mode_living_tree_site_age_days() {
62 $epoch = desktop_mode_living_tree_install_epoch();
63 if ( $epoch <= 0 ) {
64 return 0;
65 }
66 return max( 0, (int) floor( ( time() - $epoch ) / DAY_IN_SECONDS ) );
67 }
68
69 /**
70 * Recent traffic signal, resolved with the same source ladder the
71 * site-views widget uses: Jetpack Stats first, then the
72 * `_post_views_YYYY-MM-DD` post-meta convention — both summed over the
73 * last 14 days. Sites with neither simply report 0 (a windless day).
74 *
75 * The final value passes through the `desktop_mode_living_tree_traffic`
76 * filter so analytics plugins with their own counters can feed the
77 * real number in.
78 *
79 * @since 0.9.4
80 *
81 * @return int Recent view sum. >= 0.
82 */
83 function desktop_mode_living_tree_traffic() {
84 $views = desktop_mode_living_tree_jetpack_visits();
85 if ( null === $views ) {
86 $views = desktop_mode_living_tree_meta_views();
87 }
88
89 /**
90 * Filter the Living Tree traffic hormone source. Return a
91 * non-negative view count for the last ~14 days — it drives the
92 * wind (canopy sway amplitude / frequency).
93 *
94 * @since 0.9.5
95 *
96 * @param int $views Views in the window. Default: Jetpack Stats
97 * when available, else the `_post_views_*` meta
98 * sum, else 0.
99 */
100 $views = (int) apply_filters( 'desktop_mode_living_tree_traffic', $views );
101 return max( 0, $views );
102 }
103
104 /**
105 * Last-14-days visits from Jetpack Stats, or `null` when unavailable.
106 *
107 * Reads through `Automattic\Jetpack\Stats\WPCOM_Stats::get_visits()` —
108 * the same WPCOM endpoint the `jetpack/v4/stats/visits` REST route
109 * (used by the site-views widget's client) proxies, but callable
110 * server-side without a per-user capability check, so the snapshot's
111 * transient cache holds the same value no matter which user primes it.
112 * Any failure — Jetpack absent, no `get_visits` method, WP_Error,
113 * unexpected payload — returns `null` and the caller falls back to the
114 * post-views meta. A successful `0` is trusted (a quiet site is a
115 * valid answer), matching the widget's source-ladder semantics.
116 *
117 * @since 0.9.5
118 *
119 * @return int|null Views over the last 14 days, or null when Jetpack
120 * Stats can't answer.
121 */
122 function desktop_mode_living_tree_jetpack_visits() {
123 if ( ! class_exists( '\Automattic\Jetpack\Stats\WPCOM_Stats' ) ) {
124 return null;
125 }
126 $wpcom_stats = new \Automattic\Jetpack\Stats\WPCOM_Stats();
127 if ( ! method_exists( $wpcom_stats, 'get_visits' ) ) {
128 return null;
129 }
130
131 try {
132 $stats = $wpcom_stats->get_visits(
133 array(
134 'unit' => 'day',
135 'quantity' => 14,
136 )
137 );
138 } catch ( \Throwable $e ) {
139 return null;
140 }
141 if ( is_wp_error( $stats ) ) {
142 return null;
143 }
144
145 // Jetpack versions differ on object-vs-assoc-array decoding —
146 // normalise to arrays before reading.
147 $stats = json_decode( wp_json_encode( $stats ), true );
148 if ( ! is_array( $stats ) || empty( $stats['data'] ) || ! is_array( $stats['data'] ) ) {
149 return null;
150 }
151
152 // Rows are positional per the response's `fields` list — usually
153 // array( 'period', 'views' ). Locate 'views' rather than assuming.
154 $views_index = 1;
155 if ( isset( $stats['fields'] ) && is_array( $stats['fields'] ) ) {
156 $idx = array_search( 'views', $stats['fields'], true );
157 if ( false !== $idx ) {
158 $views_index = (int) $idx;
159 }
160 }
161
162 $total = 0;
163 foreach ( $stats['data'] as $row ) {
164 if ( is_array( $row ) && isset( $row[ $views_index ] ) && is_numeric( $row[ $views_index ] ) ) {
165 $total += (int) $row[ $views_index ];
166 }
167 }
168 return $total;
169 }
170
171 /**
172 * Last-14-days view sum from the `_post_views_YYYY-MM-DD` post-meta
173 * convention — the plain-WP fallback shared with the site-views
174 * widget. Sites without a view-counter plugin report 0.
175 *
176 * @since 0.9.5
177 *
178 * @return int Recent view sum. >= 0.
179 */
180 function desktop_mode_living_tree_meta_views() {
181 global $wpdb;
182
183 $total = 0;
184 $today = current_time( 'Y-m-d' );
185 for ( $i = 0; $i < 14; $i++ ) {
186 $date = gmdate( 'Y-m-d', strtotime( $today . ' -' . $i . ' days' ) );
187 $meta_key = '_post_views_' . $date;
188 $total += (int) $wpdb->get_var(
189 $wpdb->prepare(
190 "SELECT COALESCE( SUM( CAST( meta_value AS UNSIGNED ) ), 0 )
191 FROM {$wpdb->postmeta}
192 WHERE meta_key = %s",
193 $meta_key
194 )
195 );
196 }
197
198 return max( 0, $total );
199 }
200
201 /**
202 * Number of users currently online, from framework presence.
203 *
204 * @since 0.9.4
205 *
206 * @return int Count of users with `online` presence status. >= 0.
207 */
208 function desktop_mode_living_tree_active_users() {
209 if ( ! function_exists( 'desktop_mode_presence_snapshot' ) ) {
210 return 0;
211 }
212 $count = 0;
213 foreach ( desktop_mode_presence_snapshot() as $record ) {
214 if ( isset( $record['status'] ) && 'online' === $record['status'] ) {
215 $count++;
216 }
217 }
218 return $count;
219 }
220
221 /**
222 * SEO / site-health score, normalised 0..1.
223 *
224 * KNOWN GAP: unlike `traffic` (Jetpack Stats → post-views meta) and
225 * `performance` (core Site Health tallies), this hormone still has no
226 * first-party source — WordPress ships nothing SEO-shaped to read, so
227 * the default is a healthy 0.7 and the filter is the only integration
228 * point. Candidate future source: aggregate the per-post scores that
229 * SEO plugins store in post-meta into a site-wide average. Until then,
230 * an SEO or monitoring plugin that *does* know the site's health can
231 * feed the real value in via the filter.
232 *
233 * @since 0.9.4
234 *
235 * @return float Health score in [0, 1].
236 */
237 function desktop_mode_living_tree_seo_health() {
238 /**
239 * Filter the Living Tree health hormone source. Return 0..1 — it
240 * drives the canopy's colour temperature (green → yellow → red →
241 * grey).
242 *
243 * @since 0.9.4
244 *
245 * @param float $health Default 0.7.
246 */
247 $health = (float) apply_filters( 'desktop_mode_living_tree_seo_health', 0.7 );
248 return min( 1.0, max( 0.0, $health ) );
249 }
250
251 /**
252 * Performance headroom, normalised 0..1 (1 = plenty, 0 = under load).
253 *
254 * Sourced from core's own Site Health tallies when available (see
255 * {@see desktop_mode_living_tree_site_health_performance()}), falling
256 * back to a comfortable 0.8 until the weekly Site Health cron has run
257 * at least once. The filter remains the integration point for plugins
258 * with real runtime telemetry.
259 *
260 * @since 0.9.4
261 *
262 * @return float Performance score in [0, 1].
263 */
264 function desktop_mode_living_tree_performance() {
265 $performance = desktop_mode_living_tree_site_health_performance();
266 if ( null === $performance ) {
267 $performance = 0.8;
268 }
269
270 /**
271 * Filter the Living Tree performance hormone source. Return 0..1 —
272 * it throttles growth vigour.
273 *
274 * @since 0.9.4
275 *
276 * @param float $performance Default: a composite of core's Site
277 * Health issue counts when the
278 * `health-check-site-status-result`
279 * transient exists, else 0.8.
280 */
281 $performance = (float) apply_filters( 'desktop_mode_living_tree_performance', $performance );
282 return min( 1.0, max( 0.0, $performance ) );
283 }
284
285 /**
286 * Performance composite from core's Site Health tallies, or `null`
287 * when unavailable.
288 *
289 * WordPress runs every Site Health test on a weekly cron
290 * (`wp_site_health_scheduled_check`) and persists the tallies in the
291 * `health-check-site-status-result` transient as JSON counts
292 * (`good` / `recommended` / `critical`) — the same source the
293 * dashboard's Site Health widget reads. Mapping: start at 1.0,
294 * subtract 0.15 per critical issue and 0.04 per recommendation, clamp
295 * to [0.2, 1] — a clean install grows vigorously, a neglected one
296 * visibly slows down but never fully stalls.
297 *
298 * Site Health measures broad install health (PHP version, HTTPS,
299 * updates, object caching…), not pure runtime speed — the right
300 * flavour for a "growth vigour" hormone. The transient is absent on a
301 * brand-new site until the weekly cron first fires or someone opens
302 * the Site Health screen; callers fall back to the 0.8 default then.
303 *
304 * @since 0.9.5
305 *
306 * @return float|null Composite in [0.2, 1], or null when the Site
307 * Health tallies aren't available (yet).
308 */
309 function desktop_mode_living_tree_site_health_performance() {
310 $raw = get_transient( 'health-check-site-status-result' );
311 if ( is_string( $raw ) && '' !== $raw ) {
312 $counts = json_decode( $raw, true );
313 } elseif ( is_array( $raw ) ) {
314 // Defensive: some object-cache drop-ins hand back the decoded
315 // array. Core itself always stores a JSON string.
316 $counts = $raw;
317 } else {
318 return null;
319 }
320
321 if ( ! is_array( $counts )
322 || ( ! isset( $counts['critical'] ) && ! isset( $counts['recommended'] ) && ! isset( $counts['good'] ) )
323 ) {
324 return null;
325 }
326
327 $critical = max( 0, (int) ( $counts['critical'] ?? 0 ) );
328 $recommended = max( 0, (int) ( $counts['recommended'] ?? 0 ) );
329
330 $score = 1.0 - ( 0.15 * $critical ) - ( 0.04 * $recommended );
331 return min( 1.0, max( 0.2, $score ) );
332 }
333
334 /**
335 * Compact per-region structural hints (the `branches` array): published
336 * posts grouped by year, each year mapped to a depth/girth/length hint
337 * normalised against the busiest year. This is DNA, not geometry — the
338 * simulator may bias growth density with it, never position anything.
339 *
340 * @since 0.9.4
341 *
342 * @return array[] Compact branch DNA hints (max 12 entries).
343 */
344 function desktop_mode_living_tree_branch_dna() {
345 global $wpdb;
346
347 $rows = $wpdb->get_results(
348 "SELECT YEAR( post_date_gmt ) AS y, COUNT(*) AS n
349 FROM {$wpdb->posts}
350 WHERE post_status = 'publish' AND post_type = 'post'
351 AND post_date_gmt > '1970-01-01 00:00:01'
352 GROUP BY y
353 ORDER BY y ASC
354 LIMIT 12",
355 ARRAY_A
356 );
357 if ( empty( $rows ) ) {
358 return array();
359 }
360
361 $max = 1;
362 foreach ( $rows as $row ) {
363 $max = max( $max, (int) $row['n'] );
364 }
365
366 $out = array();
367 $depth = 0;
368 foreach ( $rows as $row ) {
369 $out[] = array(
370 'depth' => $depth,
371 'girth' => round( (int) $row['n'] / $max, 3 ),
372 'length' => round( min( 1.0, (int) $row['n'] / $max + 0.2 ), 3 ),
373 );
374 $depth++;
375 }
376 return $out;
377 }
378