PluginProbe
404 Solution / 4.1.19
404 Solution v4.1.19
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 / ajax / ViewBuildPendingResponseBuilder.php

ViewBuildPendingResponseBuilder.php in 404 Solution 4.1.19, at includes/ajax/ViewBuildPendingResponseBuilder.php

125 lines 4.6 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 /**
8 * Helpers for translating an `ABJ_404_Solution_ViewBuildPendingException`
9 * caught inside an AJAX handler into the same `viewBuildPending` JSON
10 * shape the cheap pre-check gate produces.
11 *
12 * Race recovery rationale: `getPaginationLinks()` and `warmTableCache()`
13 * normally call `viewDoneIsServeable()` first and short-circuit when
14 * view_done is missing. But an `invalidateViewDone()` (redirect
15 * create/edit/delete) can land between the gate and the read; the staged
16 * DAO will then throw the pending sentinel rather than running an inline
17 * build. Without this translation the exception bubbles up into the
18 * generic `catch (Throwable)` and produces an HTTP 500 / "critical error"
19 * page that the JS client cannot recover from. With it, the JS poller
20 * picks up the build via `ajaxAdvanceViewBuild` exactly as if the gate
21 * had triggered.
22 *
23 * Extracted from `ABJ_404_Solution_ViewUpdater` to keep that file under
24 * the project line-count cap.
25 */
26 class ABJ_404_Solution_ViewBuildPendingResponseBuilder {
27
28 /**
29 * Walk the throwable chain looking for a pending sentinel up to depth 5.
30 *
31 * @param Throwable $throwable
32 * @return ABJ_404_Solution_ViewBuildPendingException|null
33 */
34 public static function find(Throwable $throwable) {
35 if (!class_exists('ABJ_404_Solution_ViewBuildPendingException')) {
36 return null;
37 }
38 $current = $throwable;
39 $depth = 0;
40 while ($current !== null && $depth < 5) {
41 if ($current instanceof ABJ_404_Solution_ViewBuildPendingException) {
42 return $current;
43 }
44 $current = $current->getPrevious();
45 $depth++;
46 }
47 return null;
48 }
49
50 /**
51 * Read the build progress safely, falling back to a "stage 0/11, not
52 * yet started" shape when the DAO call throws or is unavailable.
53 *
54 * @param object $abj404dao
55 * @param ABJ_404_Solution_ViewBuildPendingException|null $pending
56 * @return array<string, mixed>
57 */
58 public static function progress($abj404dao, $pending = null) {
59 $progress = null;
60 if (is_object($abj404dao) && method_exists($abj404dao, 'getViewBuildProgress')) {
61 try {
62 $progress = $abj404dao->getViewBuildProgress();
63 } catch (Throwable $ignored) {
64 $progress = null;
65 }
66 }
67 if (!is_array($progress)) {
68 $progressText = ($pending instanceof ABJ_404_Solution_ViewBuildPendingException)
69 ? $pending->getProgressText() : 'not yet started';
70 $progress = array(
71 'status' => 'pending',
72 'stage' => 0,
73 'of' => 11,
74 'build_started' => 0,
75 'progress_text' => $progressText !== '' ? $progressText : 'not yet started',
76 );
77 }
78 return $progress;
79 }
80
81 /**
82 * Build the response shape `getPaginationLinks()` returns when the
83 * fetch path is blocked on a pending build.
84 *
85 * @param object $abj404dao
86 * @param string $subpage
87 * @param string $cacheMode
88 * @param ABJ_404_Solution_ViewBuildPendingException|null $pending
89 * @return array<string, mixed>
90 */
91 public static function fetchResponse($abj404dao, $subpage, $cacheMode, $pending = null) {
92 return array(
93 'viewBuildPending' => true,
94 'cacheMode' => $cacheMode,
95 'subpage' => $subpage,
96 'progress' => self::progress($abj404dao, $pending),
97 'message' => function_exists('__')
98 ? __('Preparing the redirects view table. Please wait.', '404-solution')
99 : 'Preparing the redirects view table. Please wait.',
100 );
101 }
102
103 /**
104 * Build the response shape `warmTableCache()` returns when the snapshot
105 * warm path is blocked on a pending build. Different from the fetch
106 * shape because the JS placeholder hydration consumes `ready=false`
107 * and the stage/stageNumber fields directly.
108 *
109 * @param object $abj404dao
110 * @param ABJ_404_Solution_ViewBuildPendingException|null $pending
111 * @return array<string, mixed>
112 */
113 public static function warmResponse($abj404dao, $pending = null) {
114 return array(
115 'status' => 'pending',
116 'ready' => false,
117 'viewBuildPending' => true,
118 'stage' => 'rows',
119 'stageNumber' => 1,
120 'queryLabel' => 'getRedirectsForView',
121 'progress' => self::progress($abj404dao, $pending),
122 );
123 }
124 }
125