| 1 |
<?php |
| 2 |
|
| 3 |
namespace Templately\Modules\PostImportFeedback\Ajax; |
| 4 |
|
| 5 |
use Templately\Modules\PostImportFeedback\Widget; |
| 6 |
use Templately\Utils\Helper; |
| 7 |
use Templately\Utils\Response\AjaxResponder; |
| 8 |
use Templately\Utils\Response\ErrorCode; |
| 9 |
use Templately\Utils\Response\ResponseNormalizer; |
| 10 |
|
| 11 |
/** |
| 12 |
* FeedbackController — independent AJAX endpoint handler, owned by |
| 13 |
* modules/post-import-feedback and supplied to full-site-import through its |
| 14 |
* `templately_fsi_ajax_handlers` filter seam (module.php::provide_ajax_handlers); |
| 15 |
* FSI executes it through its shared nonce/capability wrapper (action names |
| 16 |
* unchanged — see this spec's own Ownership note). Dropped 6 unused imports |
| 17 |
* (Elementor\Plugin, Exception, and 4 FullSiteImport\Utils\* classes) carried over |
| 18 |
* from the original monolith file, confirmed dead before removing (grep showed zero |
| 19 |
* actual usage in the method bodies below). |
| 20 |
*/ |
| 21 |
class FeedbackController { |
| 22 |
|
| 23 |
public function feedback_form() { |
| 24 |
// Get data from $_POST |
| 25 |
$review_description = isset($_POST['review-description']) ? sanitize_textarea_field($_POST['review-description']) : ''; |
| 26 |
$review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : ''; |
| 27 |
$rating = isset($_POST['rating']) ? sanitize_text_field($_POST['rating']) : ''; |
| 28 |
$pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true); |
| 29 |
|
| 30 |
// Prepare the body of the request |
| 31 |
$body = json_encode([ |
| 32 |
'description' => $review_description, |
| 33 |
'email' => $review_email, |
| 34 |
'rating' => (int) $rating, |
| 35 |
'pack_id' => (int) $pack_id, |
| 36 |
]); |
| 37 |
|
| 38 |
// Send the request to the API |
| 39 |
$response = Helper::make_api_post_request('v2/feedback/store', json_decode($body, true), [], 30); |
| 40 |
|
| 41 |
// 043 FR-003 — classification via the central normalizer, replacing |
| 42 |
// `extract_error_from_response()`'s `mixed` return (which could surface a |
| 43 |
// raw upstream body as the user's message). Behaviour is kept identical to |
| 44 |
// the REST twin in REST\Feedback::submit_feedback() — this shim exists only |
| 45 |
// for already-cached bundles, so the two must not drift. |
| 46 |
$normalized = ResponseNormalizer::normalize($response); |
| 47 |
|
| 48 |
if ($normalized->is_error()) { |
| 49 |
$error = $normalized->error(); |
| 50 |
|
| 51 |
// A repeat submission is not a failure — see the REST twin for the |
| 52 |
// live-captured `{hasFeedback:true}` 400 this handles. |
| 53 |
if (ErrorCode::ALREADY_SUBMITTED === $error->code()) { |
| 54 |
update_user_meta(get_current_user_id(), Widget::LAST_SHOWN_META, time()); |
| 55 |
AjaxResponder::success(__('Your feedback has already been submitted. Thank you!', 'templately')); |
| 56 |
return; |
| 57 |
} |
| 58 |
|
| 59 |
// The whole error, not just its text — the envelope carries the code, so the |
| 60 |
// client can branch on it instead of reading prose. |
| 61 |
AjaxResponder::error($error); |
| 62 |
return; |
| 63 |
} |
| 64 |
|
| 65 |
$payload = $normalized->payload(); |
| 66 |
$result = is_array($payload) && isset($payload['message']) ? $payload['message'] : ''; |
| 67 |
|
| 68 |
if ('' === $result) { |
| 69 |
AjaxResponder::error(ErrorCode::EMPTY_RESPONSE, __('The feedback service returned an unexpected response.', 'templately')); |
| 70 |
return; |
| 71 |
} |
| 72 |
|
| 73 |
// FR-005: a submission starts the 30-day cooldown, same as a skip/close. |
| 74 |
update_user_meta(get_current_user_id(), Widget::LAST_SHOWN_META, time()); |
| 75 |
|
| 76 |
AjaxResponder::success($result); |
| 77 |
} |
| 78 |
public function import_close_feedback_modal() { |
| 79 |
$return = null; |
| 80 |
|
| 81 |
// The dismiss reason is raw user input that gets FORWARDED TO THE CLOUD, so |
| 82 |
// it is sanitized once here rather than read twice unsanitized. The nonce is |
| 83 |
// verified by FullSiteImport's shared ajax wrapper before this handler runs. |
| 84 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 85 |
$close_action = isset($_GET['closeAction']) ? sanitize_text_field(wp_unslash($_GET['closeAction'])) : ''; |
| 86 |
|
| 87 |
if ($close_action) { |
| 88 |
$review_email = isset($_POST['review-email']) ? sanitize_email(wp_unslash($_POST['review-email'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 89 |
$pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true); |
| 90 |
|
| 91 |
// Prepare the body of the request |
| 92 |
$body = json_encode([ |
| 93 |
'action' => $close_action, |
| 94 |
'email' => $review_email, |
| 95 |
'pack_id' => (int) $pack_id, |
| 96 |
]); |
| 97 |
|
| 98 |
// Send the request to the API |
| 99 |
$response = Helper::make_api_post_request('v2/feedback/close', json_decode($body, true), [], 30); |
| 100 |
$body = wp_remote_retrieve_body($response); |
| 101 |
$return = json_decode($body, true); |
| 102 |
} |
| 103 |
update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done'); |
| 104 |
// FR-005: record when the widget was dismissed so the 30-day cooldown |
| 105 |
// (evaluated by Widget::is_eligible()) can expire on a rolling basis |
| 106 |
// instead of suppressing forever. |
| 107 |
update_user_meta(get_current_user_id(), Widget::LAST_SHOWN_META, time()); |
| 108 |
AjaxResponder::success($return); |
| 109 |
} |
| 110 |
} |
| 111 |
|