PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / DatabaseUpgradesEtcTrait_MultiSite.php

DatabaseUpgradesEtcTrait_MultiSite.php in 404 Solution 4.2.0, at includes/DatabaseUpgradesEtcTrait_MultiSite.php

294 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 trait ABJ_404_Solution_DatabaseUpgradesEtc_MultiSiteTrait {
8
9 /**
10 * Schedule a background multisite batch operation.
11 *
12 * @param string $optionPrefix e.g. 'abj404_activation' or 'abj404_upgrade'
13 * @param string $hookName e.g. 'abj404_network_activation_background'
14 * @param string $label Human-readable label for log messages, e.g. 'activation'
15 * @param int $alreadyProcessedBlogId Blog ID already processed on this request.
16 * @return void
17 */
18 private function scheduleBackgroundMultisiteBatch(string $optionPrefix, string $hookName, string $label, int $alreadyProcessedBlogId): void {
19 update_site_option($optionPrefix . '_processed_blogs', array($alreadyProcessedBlogId));
20 update_site_option($optionPrefix . '_in_progress', true);
21
22 if (wp_next_scheduled($hookName)) {
23 $this->logger->debugMessage("Background multisite $label already scheduled.");
24 return;
25 }
26
27 $scheduled = wp_schedule_single_event(time() + 30, $hookName);
28
29 if ($scheduled === false) {
30 $this->logger->errorMessage("Failed to schedule background multisite $label. Remaining sites will not be processed automatically.");
31 } else {
32 $this->logger->infoMessage("Background multisite $label scheduled successfully.");
33 }
34 }
35
36 /**
37 * Process a batch of multisite sites with the given per-site action.
38 *
39 * @param string $optionPrefix e.g. 'abj404_activation' or 'abj404_upgrade'
40 * @param string $hookName e.g. 'abj404_network_activation_background'
41 * @param string $label Human-readable label for log messages, e.g. 'activation'
42 * @param callable $perSiteAction Called for each site (receives int $siteId).
43 * @return bool True if all sites are done, false if more batches needed.
44 */
45 public function processMultisiteBatch(string $optionPrefix, string $hookName, string $label, callable $perSiteAction): bool {
46 $processedBlogs = get_site_option($optionPrefix . '_processed_blogs', array());
47 if (!is_array($processedBlogs)) {
48 $processedBlogs = array();
49 }
50
51 $allSites = get_sites(array('fields' => 'ids', 'number' => 0));
52 $remainingSites = array_diff($allSites, $processedBlogs);
53
54 if (empty($remainingSites)) {
55 delete_site_option($optionPrefix . '_processed_blogs');
56 delete_site_option($optionPrefix . '_in_progress');
57 $this->logger->infoMessage("Background multisite $label complete. All sites processed.");
58 return true;
59 }
60
61 $batchSize = 10;
62 $sitesToProcess = array_slice($remainingSites, 0, $batchSize);
63
64 $this->logger->infoMessage(sprintf(
65 "Processing multisite $label batch: %d sites (of %d remaining)",
66 count($sitesToProcess),
67 count($remainingSites)
68 ));
69
70 foreach ($sitesToProcess as $siteId) {
71 try {
72 switch_to_blog($siteId);
73 $this->logger->debugMessage(sprintf("Processing $label for site ID %d...", $siteId));
74
75 $perSiteAction((int)$siteId);
76
77 $processedBlogs[] = $siteId;
78 update_site_option($optionPrefix . '_processed_blogs', $processedBlogs);
79
80 $this->logger->debugMessage(sprintf("Successfully processed $label for site ID %d", $siteId));
81 } catch (Throwable $e) {
82 $this->logger->errorMessage(sprintf(
83 "Failed to process $label for site ID %d: %s",
84 $siteId,
85 $e->getMessage()
86 ));
87 $processedBlogs[] = $siteId;
88 update_site_option($optionPrefix . '_processed_blogs', $processedBlogs);
89 } finally {
90 restore_current_blog();
91 }
92 }
93
94 $stillRemaining = count($remainingSites) - count($sitesToProcess);
95 if ($stillRemaining > 0) {
96 $this->logger->infoMessage(sprintf(
97 "Batch complete. Rescheduling for %d remaining sites.",
98 $stillRemaining
99 ));
100 wp_schedule_single_event(time() + 30, $hookName);
101 return false;
102 } else {
103 delete_site_option($optionPrefix . '_processed_blogs');
104 delete_site_option($optionPrefix . '_in_progress');
105 $this->logger->infoMessage("Background multisite $label complete. All sites processed.");
106 return true;
107 }
108 }
109
110 /**
111 * Schedule a background activation for all network sites except the one that
112 * was just activated synchronously.
113 *
114 * @param int $alreadyProcessedBlogId Blog ID of the site already activated.
115 * @return void
116 */
117 private function scheduleBackgroundMultisiteActivation(int $alreadyProcessedBlogId): void {
118 $this->scheduleBackgroundMultisiteBatch(
119 'abj404_activation', 'abj404_network_activation_background', 'activation', $alreadyProcessedBlogId
120 );
121 }
122
123 /**
124 * Process multisite activation in batches (called by WP-Cron).
125 *
126 * Processes remaining sites that weren't handled during initial activation.
127 * Processes up to 10 sites per run to avoid timeouts, then reschedules itself
128 * if more sites remain.
129 *
130 * @return bool True if all sites processed, false if more remain
131 */
132 public function processMultisiteActivationBatch(): bool {
133 return $this->processMultisiteBatch(
134 'abj404_activation',
135 'abj404_network_activation_background',
136 'activation',
137 function (int $siteId): void {
138 add_option('abj404_settings', '', '', false);
139
140 $this->runInitialCreateTables();
141 $this->correctCollations();
142 $this->updateTableEngineToInnoDB();
143 $this->createIndexes();
144 $this->backfillRedirectsCanonicalUrl();
145 $this->renameAbj404TablesToLowerCase();
146
147 // Canonical self-heal prologue runs after schema creation so
148 // SelfHealingPrologueReachabilityTest sees per-subsite activation
149 // reach the same recovery primitives as the daily cron.
150 $this->runSelfHealPrologue();
151
152 ABJ_404_Solution_PluginLogic::doRegisterCrons();
153
154 $logic = abj_service('plugin_logic');
155 $logic->doUpdateDBVersionOption();
156 }
157 );
158 }
159
160 /**
161 * Schedule a background upgrade for all network sites except the one that
162 * was just upgraded synchronously.
163 *
164 * @param int $alreadyProcessedBlogId Blog ID of the site already upgraded.
165 * @return void
166 */
167 private function scheduleBackgroundMultisiteUpgrade(int $alreadyProcessedBlogId): void {
168 $this->scheduleBackgroundMultisiteBatch(
169 'abj404_upgrade', 'abj404_network_upgrade_background', 'upgrade', $alreadyProcessedBlogId
170 );
171 }
172
173 /**
174 * Process multisite plugin upgrade in batches (called by WP-Cron).
175 *
176 * Upgrades remaining sites that weren't handled during the initial upgrade.
177 * Processes up to 10 sites per run to avoid timeouts, then reschedules itself
178 * if more sites remain.
179 *
180 * @return bool True if all sites processed, false if more remain.
181 */
182 public function processMultisiteUpgradeBatch(): bool {
183 return $this->processMultisiteBatch(
184 'abj404_upgrade',
185 'abj404_network_upgrade_background',
186 'upgrade',
187 function (int $siteId): void {
188 // Run the full upgrade sequence for this site without going through
189 // createDatabaseTables() — that would re-schedule more background tasks.
190 $this->correctIssuesBefore();
191 $this->runInitialCreateTables();
192 $this->correctCollations();
193 $this->updateTableEngineToInnoDB();
194 $this->createIndexes();
195 $this->backfillRedirectsCanonicalUrl();
196 $this->renameAbj404TablesToLowerCase();
197 $this->correctIssuesAfter();
198
199 // Canonical self-heal prologue closes the per-subsite upgrade
200 // batch so SelfHealingPrologueReachabilityTest can prove the
201 // multisite upgrade path reaches the same recovery primitives
202 // as the daily cron tick.
203 $this->runSelfHealPrologue();
204
205 $logic = abj_service('plugin_logic');
206 $logic->doUpdateDBVersionOption();
207 }
208 );
209 }
210
211 /**
212 * Create tables for all sites in a multisite network.
213 *
214 * This function iterates through all sites in the network and creates
215 * the plugin's database tables for each site. This ensures that when
216 * the plugin is network-activated, all sites have the necessary tables.
217 *
218 * @since 3.0.1
219 */
220 /**
221 * @return void
222 * @phpstan-ignore-next-line method.unused
223 */
224 private function createTablesForAllSites() {
225 global $wpdb;
226
227 // Get all sites in the network
228 $sites = get_sites(array('fields' => 'ids', 'number' => 0));
229 $totalSites = count($sites);
230 $successCount = 0;
231 $failureCount = 0;
232
233 $this->logger->infoMessage(sprintf(
234 "Starting network-wide table creation for %d sites.",
235 $totalSites
236 ));
237
238 foreach ($sites as $siteId) {
239 try {
240 // Switch to the site
241 switch_to_blog($siteId);
242
243 $currentPrefix = $wpdb->prefix;
244 $this->logger->debugMessage(sprintf(
245 "Creating tables for site ID %d (prefix: %s)...",
246 $siteId,
247 $currentPrefix
248 ));
249
250 // Create tables for this site
251 $this->runInitialCreateTables();
252 $this->correctCollations();
253 $this->updateTableEngineToInnoDB();
254 $this->createIndexes();
255 $this->backfillRedirectsCanonicalUrl();
256
257 $successCount++;
258 $this->logger->debugMessage(sprintf(
259 "Successfully created tables for site ID %d (prefix: %s)",
260 $siteId,
261 $currentPrefix
262 ));
263
264 } catch (Throwable $e) {
265 $failureCount++;
266 $this->logger->errorMessage(sprintf(
267 "Failed to create tables for site ID %d (prefix: %s): %s",
268 $siteId,
269 $wpdb->prefix,
270 $e->getMessage()
271 ));
272 } finally {
273 // Always restore blog context
274 restore_current_blog();
275 }
276 }
277
278 // Log summary
279 $this->logger->infoMessage(sprintf(
280 "Network-wide table creation complete: %d successful, %d failed out of %d total sites.",
281 $successCount,
282 $failureCount,
283 $totalSites
284 ));
285
286 if ($failureCount > 0) {
287 $this->logger->errorMessage(sprintf(
288 "Warning: Table creation failed for %d sites. Check error logs for details.",
289 $failureCount
290 ));
291 }
292 }
293 }
294