PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / full-site-import / Concerns / HandlesAiEndpoints.php

HandlesAiEndpoints.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/full-site-import/Concerns/HandlesAiEndpoints.php

195 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\FullSiteImport\Concerns;
4
5 use Elementor\Plugin;
6 use Error;
7 use Exception;
8 use Templately\API\API;
9 use Templately\Modules\FullSiteImport\Exception\FatalErrorException;
10 use Templately\Modules\FullSiteImport\Exception\RetryableErrorException;
11 use Templately\Modules\FullSiteImport\Exception\UnknownErrorException;
12 use Templately\Modules\FullSiteImport\Utils\LogHandler;
13 use Templately\Modules\FullSiteImport\Utils\Utils;
14 use Templately\Modules\FullSiteImport\Utils\SessionData;
15 use Templately\Modules\FullSiteImport\Utils\AIUtils;
16 use Templately\Utils\Helper;
17 use Templately\Utils\Options;
18 use Templately\Utils\Response\AjaxResponder;
19 use Templately\Utils\Response\ErrorCode;
20
21 /**
22 * HandlesAiEndpoints — extracted verbatim from FullSiteImport (behavior-preserving).
23 * Composed back into FullSiteImport via `use`; $this and method resolution unchanged.
24 */
25 trait HandlesAiEndpoints {
26 /**
27 * Frozen AJAX action `templately_pack_ai_get_json` — a thin alias to the
28 * single retrieval behavior (D7). Kept so the action name contract is stable.
29 */
30 public function ai_get_json() {
31 $this->retrieve_ai_content();
32 }
33
34 /**
35 * The ONE content-retrieval behavior (FR-002/D7): read whatever AI content is
36 * on disk for the process and respond with the standard envelope (FR-003).
37 * Both `ai_get_json` (read) and `ai_poll_template` (poll-then-read) flow
38 * through here. Responses are ADDITIVE — the existing `process_id`/`templates`/
39 * `is_last_part` payload is preserved, with `{success,terminal,code,message}`
40 * merged in (envelope sent INSIDE wp_send_json's data, where the client's poll
41 * loop already reads `terminal` from the error path).
42 */
43 private function retrieve_ai_content() {
44 // read json data from post body
45 $body = file_get_contents('php://input');
46 $data = json_decode($body, true);
47
48 if(empty($data['ai_page_ids'])){
49 AjaxResponder::error(ErrorCode::AI_INVALID_PROCESS, 'Invalid ai_page_ids');
50 return;
51 }
52
53 if(!isset($_GET['session_id'])){
54 AjaxResponder::error(ErrorCode::AI_INVALID_PROCESS, 'Invalid session_id');
55 return;
56 }
57
58 $session_id = isset($_GET['session_id']) ? sanitize_text_field($_GET['session_id']) : null;
59 $process_id = $data['process_id'] ?? null;
60 $ai_page_ids = $data['ai_page_ids'] ?? null;
61
62 $this->request_params = $this->get_session_data();
63 try {
64 $this->manifest = $this->read_manifest($this->request_params['dir_path']);
65 } catch (\Exception $th) {
66 AjaxResponder::error(ErrorCode::AI_INTERNAL_ERROR, $th->getMessage());
67 }
68
69 if(!empty($session_id) && empty($process_id)){
70 if ( !empty($this->request_params['process_id']) ){
71 $process_id = $this->request_params['process_id'] ?? null;
72 } else {
73 $process_id = AIUtils::get_ai_process_id_by_session_id($session_id);
74 }
75 }
76
77 if(empty($process_id)){
78 AjaxResponder::error(ErrorCode::AI_INVALID_PROCESS, 'Invalid process_id');
79 return;
80 }
81
82 $process_data = AIUtils::get_ai_process_data_by_process_id($process_id);
83 if (!empty($process_data['preview_error'])) {
84 // Terminal remote failure persisted on the process row.
85 AjaxResponder::error(ErrorCode::AI_REMOTE_FAILED, $process_data['preview_error']);
86 }
87
88 // Use the new common function to read AI template data directly
89 $result = AIUtils::read_ai_template_data($session_id, $ai_page_ids, $this->request_params['dir_path']);
90
91 // Check if this is called from polling endpoint and include additional data
92 $response_data = ['process_id' => $process_id, 'templates' => $result];
93
94 $is_last_part = null;
95 if (isset($this->polling_is_last_part)) {
96 $is_last_part = $this->polling_is_last_part;
97 $response_data['is_last_part'] = $is_last_part;
98
99 // Clean up the polling property
100 unset($this->polling_is_last_part);
101 }
102
103 // Code reflects completeness: all pages on disk (or remote says last part)
104 // → ok (terminal); otherwise not_ready (the client keeps polling).
105 $total_pages = 0;
106 if (is_array($ai_page_ids)) {
107 foreach ($ai_page_ids as $ids) {
108 $total_pages += is_array($ids) ? count($ids) : 0;
109 }
110 }
111 $ready = ! empty($is_last_part) || ($total_pages > 0 && count($result) >= $total_pages);
112
113 // The one place the whole generation's progress is actually countable. Logged on
114 // every poll at debug, and raised to a warning for the state that strands the
115 // client: the remote says this was the last part, yet pages are still unwritten.
116 // The client turns exactly that into "N pages still missing when process marked as
117 // complete" and stops — so without this line the only record of WHY is a message in
118 // the browser.
119 $on_disk = is_array($result) ? count($result) : 0;
120 $stranded = ! empty($is_last_part) && $total_pages > 0 && $on_disk < $total_pages;
121 Helper::log(
122 sprintf(
123 'retrieve_ai_content: %s — %d/%d page(s) on disk, is_last_part=%s%s',
124 $process_id,
125 $on_disk,
126 $total_pages,
127 null === $is_last_part ? '(not polled)' : ( $is_last_part ? 'yes' : 'no' ),
128 $stranded ? ' — STRANDED: marked complete with pages missing' : ''
129 ),
130 'retrieve_ai_content',
131 $stranded ? 'warning' : 'debug'
132 );
133
134 AjaxResponder::success(API::ai_envelope($ready ? 'ok' : 'not_ready', '', $response_data));
135 }
136
137 /**
138 * AJAX handler for polling AI template generation status on local sites
139 * Makes GET request to API endpoint and returns data in same format as ai_get_json()
140 */
141 public function ai_poll_template() {
142 // Read JSON data from post body
143 $body = file_get_contents('php://input');
144 $data = json_decode($body, true);
145
146 $process_id = $data['process_id'] ?? null;
147 $ai_page_ids = $data['ai_page_ids'] ?? null;
148
149 if(empty($process_id)){
150 AjaxResponder::error(ErrorCode::AI_INVALID_PROCESS, 'Invalid process_id');
151 return;
152 }
153
154 // Validate and get AI process data using centralized method
155 $process_data = AIUtils::validate_and_get_process_data($process_id);
156 if (is_wp_error($process_data)) {
157 $this->retrieve_ai_content();
158 return;
159 }
160
161 $session_id = $process_data['session_id'];
162 $ai_page_ids = $process_data['ai_page_ids'];
163
164 // Use the common polling function to handle all template processing
165 $polling_result = AIUtils::poll_for_template($process_id, $session_id, $ai_page_ids);
166
167 // Terminal remote error (401/403/404): stop polling and report a real error.
168 // Still HTTP 200 (AjaxResponder always is), but the body is the standard
169 // error envelope now, so it REJECTS on the client instead of resolving with
170 // `success:false`. The poll loop stops on the error's `retryable:false`
171 // rather than on a bespoke `terminal` flag — see pollAIContent's
172 // onAttemptError. FR-003.
173 if (is_wp_error($polling_result)) {
174 $error_data = $polling_result->get_error_data();
175 AjaxResponder::error(ErrorCode::AI_REMOTE_FAILED, $polling_result->get_error_message(), [
176 'status' => isset($error_data['status']) ? $error_data['status'] : null,
177 'context' => [ 'process_id' => $process_id ],
178 ]);
179 return;
180 }
181
182 if (!$polling_result) {
183 // Transient miss — fall back to the read path (keep waiting).
184 $this->retrieve_ai_content();
185 return;
186 }
187
188 // After polling and processing templates, read + respond via the single
189 // retrieval behavior (reuses all the existing logic without duplication).
190 $this->retrieve_ai_content();
191 }
192
193
194 }
195