PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.3.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.3.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / seo / class-metadata-pending.php

class-metadata-pending.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.3.0, at includes/seo/class-metadata-pending.php

149 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * "Metadata is about to be written" marker.
4 *
5 * The editor SEO panel has no push channel, so historically it polled
6 * `/metadata/{id}` blind for ~45s on every editor open, waiting for a
7 * background writer (Auto AI on publish, imports) that in the overwhelming
8 * majority of sessions was never coming. That cost every user 11+ requests per
9 * open and, on slow hosts, piled up into 503s (#329).
10 *
11 * This flips the panel from "poll and hope" to "poll only when something is
12 * genuinely in flight". A writer marks the post before it queues work and
13 * clears it when done; the metadata endpoint reports the flag; the panel polls
14 * only while it is set.
15 *
16 * Two markers, because the writers come in two shapes:
17 *
18 * - Per post (`mark()`), for a write announced ahead of time for one known
19 * post — Auto AI, which queues a cron run on publish.
20 * - Site wide (`mark_bulk()`), for a job writing across many posts as it goes —
21 * the SEO importer. Marking each post individually would cost one transient
22 * per post for a window of microseconds (the write is synchronous), which
23 * buys nothing; the job announces itself once for as long as it runs.
24 *
25 * A transient rather than post meta deliberately: this is ephemeral run state,
26 * it must not pollute the post's meta, and it must expire on its own if a cron
27 * run dies before clearing it.
28 *
29 * @package ThinkRank\SEO
30 * @since 1.30.0
31 */
32
33 declare(strict_types=1);
34
35 namespace ThinkRank\SEO;
36
37 if (!defined('ABSPATH')) {
38 exit;
39 }
40
41 /**
42 * Tracks posts with a background metadata write in flight.
43 */
44 class Metadata_Pending {
45
46 /**
47 * Transient key prefix.
48 */
49 private const PREFIX = 'thinkrank_meta_pending_';
50
51 /**
52 * Self-expiry, so a cron run that dies before clearing cannot leave a post
53 * polling forever. Comfortably longer than the ~15s Auto AI delay plus the
54 * AI call itself.
55 */
56 private const TTL = 5 * MINUTE_IN_SECONDS;
57
58 /**
59 * Transient key for a site-wide job (see the class docblock).
60 */
61 private const BULK_KEY = 'thinkrank_meta_pending_bulk';
62
63 /**
64 * Kept short: every editor open polls while it is set, so a job that dies
65 * between chunks must stop costing requests quickly. Each chunk re-marks,
66 * which is what keeps a running job's window open.
67 */
68 private const BULK_TTL = 2 * MINUTE_IN_SECONDS;
69
70 /**
71 * Mark a post as having a background metadata write in flight.
72 *
73 * Call this *before* queueing the work, so the flag is already visible to
74 * an editor that reloads immediately after publish.
75 *
76 * @param int $post_id Post being written to.
77 * @return void
78 */
79 public static function mark(int $post_id): void {
80 if ($post_id <= 0) {
81 return;
82 }
83 set_transient(self::PREFIX . $post_id, 1, self::TTL);
84 }
85
86 /**
87 * Clear the marker once the write has finished (or definitively failed).
88 *
89 * @param int $post_id Post that was being written to.
90 * @return void
91 */
92 public static function clear(int $post_id): void {
93 if ($post_id <= 0) {
94 return;
95 }
96 delete_transient(self::PREFIX . $post_id);
97 }
98
99 /**
100 * Mark a site-wide metadata write job as running.
101 *
102 * Re-marking is how a chunked job holds its window open, so call this at
103 * the start of every chunk that writes, not just the first.
104 *
105 * @return void
106 */
107 public static function mark_bulk(): void {
108 set_transient(self::BULK_KEY, 1, self::BULK_TTL);
109 }
110
111 /**
112 * Clear the site-wide job marker once the job has finished.
113 *
114 * @return void
115 */
116 public static function clear_bulk(): void {
117 delete_transient(self::BULK_KEY);
118 }
119
120 /**
121 * Whether a background metadata write is in flight for this post.
122 *
123 * True while either this post has its own marker or a site-wide job is
124 * running. Filterable so features that own their own queue (Pro) can
125 * report pending state without going through `mark()`.
126 *
127 * @param int $post_id Post to check.
128 * @return bool
129 */
130 public static function is_pending(int $post_id): bool {
131 $pending = false !== get_transient(self::PREFIX . $post_id)
132 || false !== get_transient(self::BULK_KEY);
133
134 /**
135 * Filter whether a background metadata write is pending for a post.
136 *
137 * Returning true keeps the editor SEO panel polling for the value;
138 * returning false stops it. Keep any implementation cheap — this runs
139 * on every metadata request.
140 *
141 * @since 1.30.0
142 *
143 * @param bool $pending Whether a write is in flight.
144 * @param int $post_id Post being checked.
145 */
146 return (bool) apply_filters('thinkrank_metadata_pending', $pending, $post_id);
147 }
148 }
149