PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
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 1.11.0 All 47 releases
thinkrank / includes / api / class-email-report-endpoint.php

class-email-report-endpoint.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/api/class-email-report-endpoint.php

206 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email Report REST Endpoint
4 *
5 * Three routes:
6 * GET /thinkrank/v1/email-report/config — returns the resolved config + section catalog
7 * POST /thinkrank/v1/email-report/config — switches the report on or off
8 * POST /thinkrank/v1/email-report/test-send — triggers an immediate one-off send
9 *
10 * Permissions: admin (`manage_options`) + valid REST nonce.
11 *
12 * @package ThinkRank
13 * @subpackage API
14 * @since 1.9.0
15 */
16
17 declare(strict_types=1);
18
19 namespace ThinkRank\API;
20
21 use ThinkRank\API\Traits\CSRF_Protection;
22 use ThinkRank\SEO\Email_Report_Manager;
23 use WP_REST_Controller;
24 use WP_REST_Request;
25 use WP_REST_Response;
26 use WP_Error;
27
28 require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-csrf-protection.php';
29
30 if (!defined('ABSPATH')) {
31 exit;
32 }
33
34 /**
35 * Email_Report_Endpoint
36 *
37 * @since 1.9.0
38 */
39 final class Email_Report_Endpoint extends WP_REST_Controller {
40 use CSRF_Protection;
41
42 protected $namespace = 'thinkrank/v1';
43 protected $rest_base = 'email-report';
44
45 private ?Email_Report_Manager $manager = null;
46
47 public function register_routes(): void {
48 register_rest_route(
49 $this->namespace,
50 '/' . $this->rest_base . '/config',
51 [
52 [
53 'methods' => 'GET',
54 'callback' => [$this, 'get_config'],
55 'permission_callback' => [$this, 'check_admin_read_permissions'],
56 ],
57 [
58 'methods' => 'POST',
59 'callback' => [$this, 'save_config'],
60 'permission_callback' => [$this, 'check_admin_csrf_permissions'],
61 'args' => [
62 'enabled' => [
63 'type' => 'boolean',
64 'sanitize_callback' => 'rest_sanitize_boolean',
65 ],
66 ],
67 ],
68 ]
69 );
70
71 register_rest_route(
72 $this->namespace,
73 '/' . $this->rest_base . '/test-send',
74 [
75 [
76 'methods' => 'POST',
77 'callback' => [$this, 'test_send'],
78 'permission_callback' => [$this, 'check_admin_csrf_permissions'],
79 ],
80 ]
81 );
82 }
83
84 /**
85 * GET /email-report/config
86 *
87 * Returns the resolved config (on/off, frequency, recipients, sections)
88 * along with the section catalog and the next scheduled run.
89 */
90 public function get_config(WP_REST_Request $request): WP_REST_Response {
91 $manager = $this->resolve_manager();
92 if ($manager === null) {
93 return new WP_REST_Response([
94 'error' => __('Email Report Manager unavailable.', 'thinkrank'),
95 ], 500);
96 }
97
98 return new WP_REST_Response([
99 'config' => $manager->config()->get(),
100 'sections' => $manager->registry()->describe_for_ui(),
101 'next_run' => $manager->scheduler()->next_run_iso(),
102 ]);
103 }
104
105 /**
106 * POST /email-report/config
107 */
108 public function save_config(WP_REST_Request $request): WP_REST_Response {
109 $manager = $this->resolve_manager();
110 if ($manager === null) {
111 return new WP_REST_Response([
112 'success' => false,
113 'message' => __('Email Report Manager unavailable.', 'thinkrank'),
114 ], 500);
115 }
116
117 $input = [];
118 if ($request->has_param('enabled')) {
119 $input['enabled'] = (bool) $request->get_param('enabled');
120 }
121
122 $saved = $manager->config()->save($input);
123
124 return new WP_REST_Response([
125 'success' => true,
126 'config' => $saved,
127 'next_run' => $manager->scheduler()->next_run_iso(),
128 ]);
129 }
130
131 /**
132 * POST /email-report/test-send
133 */
134 public function test_send(WP_REST_Request $request): WP_REST_Response {
135 $manager = $this->resolve_manager();
136 if ($manager === null) {
137 return new WP_REST_Response([
138 'success' => false,
139 'message' => __('Email Report Manager unavailable.', 'thinkrank'),
140 ], 500);
141 }
142
143 $result = $manager->generator()->generate_test();
144
145 $status = !empty($result['success']) ? 200 : 400;
146 return new WP_REST_Response([
147 'success' => (bool) ($result['success'] ?? false),
148 'result' => $result,
149 ], $status);
150 }
151
152 /**
153 * Permission for read endpoints. Same admin gate, but no CSRF
154 * (GET requests don't require it).
155 */
156 public function check_admin_read_permissions(WP_REST_Request $request) {
157 if (!is_user_logged_in()) {
158 return new WP_Error('rest_forbidden', __('Not logged in.', 'thinkrank'), ['status' => 401]);
159 }
160 if (!current_user_can('manage_options')) {
161 return new WP_Error('rest_forbidden', __('Insufficient permissions.', 'thinkrank'), ['status' => 403]);
162 }
163 return true;
164 }
165
166 /**
167 * Permission for the state-changing POST endpoints (save config / test-send).
168 *
169 * These write the site-global report config and can trigger a send of private
170 * analytics, so they require admin (manage_options) plus CSRF verification —
171 * NOT the shared edit_posts-level check_csrf_permissions() trait, which would
172 * let a Contributor overwrite the config and exfiltrate the report. Matches the
173 * manage_options gate on the GET route.
174 */
175 public function check_admin_csrf_permissions(WP_REST_Request $request) {
176 if (!is_user_logged_in()) {
177 return new WP_Error('rest_forbidden', __('Not logged in.', 'thinkrank'), ['status' => 401]);
178 }
179 if (!current_user_can('manage_options')) {
180 return new WP_Error('rest_forbidden', __('Insufficient permissions.', 'thinkrank'), ['status' => 403]);
181 }
182 if (!$this->verify_request_nonce($request)) {
183 return new WP_Error('rest_forbidden', __('Invalid security token. Please refresh the page and try again.', 'thinkrank'), ['status' => 403]);
184 }
185 return true;
186 }
187
188 /**
189 * Reach into the plugin DI container for the Email_Report_Manager
190 * instance built at boot.
191 */
192 private function resolve_manager(): ?Email_Report_Manager {
193 if ($this->manager !== null) {
194 return $this->manager;
195 }
196 if (function_exists('thinkrank')) {
197 $component = thinkrank()->get_component('email_report');
198 if ($component instanceof Email_Report_Manager) {
199 $this->manager = $component;
200 return $this->manager;
201 }
202 }
203 return null;
204 }
205 }
206