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 / ai-copilot / hooks.php

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

150 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 * Desktop Mode — 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 * `includes/comments-window/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 (`desktop_mode_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 WPDesktopMode
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 * @since 0.5.0
49 *
50 * @param string $hook Cron hook name, e.g. 'desktop_mode_ai_analyze_comment'.
51 * @param array $args Arguments passed to the hook callback.
52 * @param string $dedup_key Unique string used to build the transient key.
53 */
54 function desktop_mode_ai_schedule_job( $hook, array $args, $dedup_key ) {
55 $transient = 'desktop_mode_ai_q_' . md5( $dedup_key );
56
57 if ( get_transient( $transient ) ) {
58 return; // Already queued within the guard window — skip.
59 }
60
61 // Schedule via WP-Cron for production environments.
62 wp_schedule_single_event( time(), $hook, $args );
63
64 // Mark as queued before the shutdown handler fires so re-entrant
65 // saves (e.g. a meta update during analysis) don't double-queue.
66 set_transient( $transient, 1, 120 );
67
68 // Run on shutdown — covers Docker dev environments and WP-CLI where
69 // WP-Cron's loopback HTTP request cannot reach the site.
70 // PHP_INT_MAX priority ensures we run last, after WordPress has
71 // finished any pending DB writes from the current request.
72 add_action(
73 'shutdown',
74 static function () use ( $hook, $args ) {
75 // Send the HTTP response to the browser before the
76 // (potentially slow) provider call so the editor stays
77 // responsive. fastcgi_finish_request() is a PHP-FPM
78 // function; in other SAPIs (CLI, Apache mod_php) it is
79 // not available and we proceed without it — the analysis
80 // still runs, it just blocks the request exit briefly.
81 if ( function_exists( 'fastcgi_finish_request' ) ) {
82 fastcgi_finish_request();
83 }
84
85 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- generic dispatcher; caller passes a desktop_mode_* hook name.
86 do_action_ref_array( $hook, $args );
87 },
88 PHP_INT_MAX
89 );
90 }
91
92 /**
93 * Returns the user ID to attribute the request to, trying three sources
94 * in priority order:
95 *
96 * 1. The currently logged-in user (HTTP request context).
97 * 2. A provided fallback ID (e.g. post author).
98 * 3. The first administrator who has the AI assistant enabled — covers
99 * anonymous comments, WP-CLI imports, and REST API requests without an
100 * authenticated user context.
101 *
102 * @since 0.5.0
103 *
104 * @param int $fallback_user_id Author/owner to try when no current user.
105 * @return int User ID, or 0 if none could be found.
106 */
107 function desktop_mode_ai_resolve_user_id( $fallback_user_id = 0 ) {
108 $uid = get_current_user_id();
109 if ( $uid > 0 ) {
110 return $uid;
111 }
112
113 $fallback = (int) $fallback_user_id;
114 if ( $fallback > 0 ) {
115 return $fallback;
116 }
117
118 // Last resort: any administrator with the assistant enabled. Scans the
119 // first 20 admins to avoid a full table scan on large sites.
120 return desktop_mode_ai_find_enabled_user();
121 }
122
123 /**
124 * Returns the first administrator user ID that has the AI assistant enabled.
125 *
126 * Used as a last-resort fallback for anonymous comments, WP-CLI imports,
127 * and other contexts where no user session is available.
128 *
129 * @since 0.5.0
130 *
131 * @return int User ID, or 0 if none found.
132 */
133 function desktop_mode_ai_find_enabled_user() {
134 $admin_ids = get_users(
135 array(
136 'role' => 'administrator',
137 'number' => 20,
138 'fields' => 'ID',
139 )
140 );
141
142 foreach ( $admin_ids as $uid ) {
143 if ( desktop_mode_ai_is_enabled( (int) $uid ) ) {
144 return (int) $uid;
145 }
146 }
147
148 return 0;
149 }
150