PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.8
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 / ai-copilot / hooks.php

hooks.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.8, at includes/ai-copilot/hooks.php

144 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — AI Copilot job-scheduling helpers.
4 *
5 * Provides the async-job scheduler and user-resolution helpers used by the
6 * Comments-window moderation feature (see
7 * `apps/comments/parts/ai-moderation.php`) to queue comment analysis
8 * outside the HTTP request that triggered the comment. Moderation stays
9 * responsive even when the provider is slow.
10 *
11 * Comment analysis is the only auto-analysis the copilot performs — it feeds
12 * the comments-window spam score, and only when the (opt-in, off-by-default)
13 * Comments AI toggle is on. Posts, pages, and taxonomy terms are NOT analyzed;
14 * the AI assistant finds them with native WordPress keyword search instead
15 * (see search.php).
16 *
17 * Deduplication: a 120-second transient (`openstation_ai_q_<md5 of
18 * '{type}_{id}'>`) prevents the same comment from being queued twice when
19 * WordPress fires the hook multiple times in one request.
20 *
21 * @package OpenStation
22 */
23
24 defined( 'ABSPATH' ) || exit;
25
26 // ---------------------------------------------------------------------------
27 // Helpers
28 // ---------------------------------------------------------------------------
29
30 /**
31 * Schedules an AI analysis job and ensures it runs even in environments
32 * where WP-Cron's HTTP-based spawn_cron() cannot reach the site
33 * (e.g. Docker dev setups where localhost:PORT doesn't resolve from
34 * inside the container).
35 *
36 * Two-track approach:
37 * 1. WP-Cron: reliable in production with a system cron or a host
38 * that can make loopback HTTP requests.
39 * 2. Shutdown handler: runs the job in the same PHP process, after
40 * the HTTP response has been sent to the browser via
41 * fastcgi_finish_request() (available in PHP-FPM, which Docker
42 * environments use). Falls back to running after the request in
43 * non-FPM setups (e.g. WP-CLI).
44 *
45 * The deduplication transient prevents the same entity from being
46 * queued and run twice within the guard window.
47 *
48 * @param string $hook Cron hook name, e.g. 'desktop_mode_ai_analyze_comment'.
49 * @param array $args Arguments passed to the hook callback.
50 * @param string $dedup_key Unique string used to build the transient key.
51 */
52 function openstation_ai_schedule_job( $hook, array $args, $dedup_key ) {
53 $transient = 'openstation_ai_q_' . md5( $dedup_key );
54
55 if ( get_transient( $transient ) ) {
56 return; // Already queued within the guard window — skip.
57 }
58
59 // Schedule via WP-Cron for production environments.
60 wp_schedule_single_event( time(), $hook, $args );
61
62 // Mark as queued before the shutdown handler fires so re-entrant
63 // saves (e.g. a meta update during analysis) don't double-queue.
64 set_transient( $transient, 1, 120 );
65
66 // Run on shutdown — covers Docker dev environments and WP-CLI where
67 // WP-Cron's loopback HTTP request cannot reach the site.
68 // PHP_INT_MAX priority ensures we run last, after WordPress has
69 // finished any pending DB writes from the current request.
70 add_action(
71 'shutdown',
72 static function () use ( $hook, $args ) {
73 // Send the HTTP response to the browser before the
74 // (potentially slow) provider call so the editor stays
75 // responsive. fastcgi_finish_request() is a PHP-FPM
76 // function; in other SAPIs (CLI, Apache mod_php) it is
77 // not available and we proceed without it — the analysis
78 // still runs, it just blocks the request exit briefly.
79 if ( function_exists( 'fastcgi_finish_request' ) ) {
80 fastcgi_finish_request();
81 }
82
83 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- generic dispatcher; caller passes a openstation_* hook name.
84 do_action_ref_array( $hook, $args );
85 },
86 PHP_INT_MAX
87 );
88 }
89
90 /**
91 * Returns the user ID to attribute the request to, trying three sources
92 * in priority order:
93 *
94 * 1. The currently logged-in user (HTTP request context).
95 * 2. A provided fallback ID (e.g. post author).
96 * 3. The first administrator who has the AI assistant enabled — covers
97 * anonymous comments, WP-CLI imports, and REST API requests without an
98 * authenticated user context.
99 *
100 * @param int $fallback_user_id Author/owner to try when no current user.
101 * @return int User ID, or 0 if none could be found.
102 */
103 function openstation_ai_resolve_user_id( $fallback_user_id = 0 ) {
104 $uid = get_current_user_id();
105 if ( $uid > 0 ) {
106 return $uid;
107 }
108
109 $fallback = (int) $fallback_user_id;
110 if ( $fallback > 0 ) {
111 return $fallback;
112 }
113
114 // Last resort: any administrator with the assistant enabled. Scans the
115 // first 20 admins to avoid a full table scan on large sites.
116 return openstation_ai_find_enabled_user();
117 }
118
119 /**
120 * Returns the first administrator user ID that has the AI assistant enabled.
121 *
122 * Used as a last-resort fallback for anonymous comments, WP-CLI imports,
123 * and other contexts where no user session is available.
124 *
125 * @return int User ID, or 0 if none found.
126 */
127 function openstation_ai_find_enabled_user() {
128 $admin_ids = get_users(
129 array(
130 'role' => 'administrator',
131 'number' => 20,
132 'fields' => 'ID',
133 )
134 );
135
136 foreach ( $admin_ids as $uid ) {
137 if ( openstation_ai_is_enabled( (int) $uid ) ) {
138 return (int) $uid;
139 }
140 }
141
142 return 0;
143 }
144