PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / API / CampaignAPI.php

CampaignAPI.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.2.1, at includes/API/CampaignAPI.php

375 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\API;
4
5 use Better_Payment\Lite\Campaign\CampaignStats;
6 use Better_Payment\Lite\Campaign\MetaBox;
7 use Better_Payment\Lite\Campaign\Templates\TemplateManager;
8 use Better_Payment\Lite\Admin\DB;
9 use Better_Payment\Lite\Campaign\Services\RendererService;
10 use WP_Error;
11 use WP_REST_Controller;
12 use WP_REST_Request;
13 use WP_REST_Response;
14 use WP_REST_Server;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * REST API routes for bp_campaign posts and campaign stats.
22 * Namespace: better-payment/v1
23 */
24 class CampaignAPI extends WP_REST_Controller {
25
26 protected $namespace = 'better-payment/v1';
27
28 public function __construct() {
29 add_action( 'rest_api_init', [ $this, 'register_routes' ] );
30 }
31
32 public function register_routes() {
33 // List campaigns
34 register_rest_route( $this->namespace, '/campaigns', [
35 [
36 'methods' => WP_REST_Server::READABLE,
37 'callback' => [ $this, 'get_campaigns' ],
38 'permission_callback' => [ $this, 'check_admin_permissions' ],
39 ],
40 [
41 'methods' => WP_REST_Server::CREATABLE,
42 'callback' => [ $this, 'create_campaign' ],
43 'permission_callback' => [ $this, 'check_admin_permissions' ],
44 ],
45 ] );
46
47 // Restore campaign from trash
48 register_rest_route( $this->namespace, '/campaigns/(?P<id>[\d]+)/restore', [
49 'methods' => WP_REST_Server::CREATABLE,
50 'callback' => [ $this, 'restore_campaign' ],
51 'permission_callback' => [ $this, 'check_admin_permissions' ],
52 'args' => [ 'id' => [ 'type' => 'integer' ] ],
53 ] );
54
55 // Single campaign
56 register_rest_route( $this->namespace, '/campaigns/(?P<id>[\d]+)', [
57 [
58 'methods' => WP_REST_Server::READABLE,
59 'callback' => [ $this, 'get_campaign' ],
60 'permission_callback' => [ $this, 'check_admin_permissions' ],
61 'args' => [ 'id' => [ 'type' => 'integer' ] ],
62 ],
63 [
64 'methods' => WP_REST_Server::EDITABLE,
65 'callback' => [ $this, 'update_campaign' ],
66 'permission_callback' => [ $this, 'check_admin_permissions' ],
67 'args' => [ 'id' => [ 'type' => 'integer' ] ],
68 ],
69 [
70 'methods' => WP_REST_Server::DELETABLE,
71 'callback' => [ $this, 'delete_campaign' ],
72 'permission_callback' => [ $this, 'check_admin_permissions' ],
73 'args' => [ 'id' => [ 'type' => 'integer' ] ],
74 ],
75 ] );
76
77 // Campaign stats (public — used for frontend display)
78 register_rest_route( $this->namespace, '/campaigns/(?P<id>[\d]+)/stats', [
79 'methods' => WP_REST_Server::READABLE,
80 'callback' => [ $this, 'get_campaign_stats' ],
81 'permission_callback' => '__return_true',
82 'args' => [ 'id' => [ 'type' => 'integer' ] ],
83 ] );
84
85 // Available templates — public (builder needs these before user is authenticated)
86 register_rest_route( $this->namespace, '/campaigns/templates', [
87 'methods' => WP_REST_Server::READABLE,
88 'callback' => [ $this, 'get_templates' ],
89 'permission_callback' => '__return_true',
90 ] );
91
92 // Builder live-preview — renders layout+meta as a full HTML document (no saved post required)
93 register_rest_route( $this->namespace, '/campaigns/preview', [
94 'methods' => WP_REST_Server::CREATABLE,
95 'callback' => [ $this, 'preview_campaign_builder' ],
96 'permission_callback' => [ $this, 'check_admin_permissions' ],
97 ] );
98
99 // Preview — renders layout JSON as HTML without saving
100 register_rest_route( $this->namespace, '/campaigns/(?P<id>[\d]+)/preview', [
101 'methods' => WP_REST_Server::CREATABLE,
102 'callback' => [ $this, 'preview_campaign' ],
103 'permission_callback' => [ $this, 'check_admin_permissions' ],
104 'args' => [ 'id' => [ 'type' => 'integer' ] ],
105 ] );
106
107 // Template preview — renders a template definition as HTML for the picker iframe
108 register_rest_route( $this->namespace, '/templates/(?P<key>[a-z0-9\-]+)/preview', [
109 'methods' => WP_REST_Server::READABLE,
110 'callback' => [ $this, 'preview_template' ],
111 'permission_callback' => [ $this, 'check_admin_permissions' ],
112 ] );
113 }
114
115 // ------------------------------------------------------------------ handlers
116
117 public function get_campaigns( WP_REST_Request $request ): WP_REST_Response {
118 $status = sanitize_text_field( $request->get_param( 'status' ) ?: 'all' );
119 $search = sanitize_text_field( $request->get_param( 's' ) ?: '' );
120 $orderby = in_array( $request->get_param( 'orderby' ), [ 'title', 'date' ], true ) ? $request->get_param( 'orderby' ) : 'date';
121 $order = strtoupper( $request->get_param( 'order' ) ?: 'DESC' ) === 'ASC' ? 'ASC' : 'DESC';
122 $per_page = max( 1, (int) ( $request->get_param( 'per_page' ) ?: 20 ) );
123 $page = max( 1, (int) ( $request->get_param( 'page' ) ?: 1 ) );
124
125 if ( $status === 'trash' ) {
126 $post_statuses = [ 'trash' ];
127 } elseif ( in_array( $status, [ 'publish', 'draft' ], true ) ) {
128 $post_statuses = [ $status ];
129 } else {
130 $post_statuses = [ 'publish', 'draft' ];
131 }
132
133 $args = [
134 'post_type' => 'bp_campaign',
135 'post_status' => $post_statuses,
136 'posts_per_page' => $per_page,
137 'paged' => $page,
138 'orderby' => $orderby,
139 'order' => $order,
140 's' => $search,
141 ];
142
143 $query = new \WP_Query( $args );
144 $data = array_map( [ $this, 'prepare_campaign_response' ], $query->posts );
145
146 $post_counts = wp_count_posts( 'bp_campaign' );
147 $counts = [
148 'all' => (int) ( $post_counts->publish ?? 0 ) + (int) ( $post_counts->draft ?? 0 ),
149 'publish' => (int) ( $post_counts->publish ?? 0 ),
150 'draft' => (int) ( $post_counts->draft ?? 0 ),
151 'trash' => (int) ( $post_counts->trash ?? 0 ),
152 ];
153
154 return new WP_REST_Response( [
155 'campaigns' => $data,
156 'total' => (int) $query->found_posts,
157 'pages' => (int) $query->max_num_pages,
158 'page' => $page,
159 'counts' => $counts,
160 ], 200 );
161 }
162
163 public function create_campaign( WP_REST_Request $request ): WP_REST_Response {
164 if ( ! $this->valid_nonce( $request ) ) {
165 return new WP_REST_Response( [ 'message' => 'Invalid nonce' ], 403 );
166 }
167
168 $title = sanitize_text_field( $request->get_param( 'title' ) ?: __( 'New Campaign', 'better-payment' ) );
169 $post_status = sanitize_text_field( $request->get_param( 'post_status' ) ?: 'draft' );
170
171 $post_id = wp_insert_post( [
172 'post_type' => 'bp_campaign',
173 'post_title' => $title,
174 'post_status' => $post_status,
175 ] );
176
177 if ( is_wp_error( $post_id ) ) {
178 return new WP_REST_Response( [ 'message' => $post_id->get_error_message() ], 500 );
179 }
180
181 MetaBox::save_from_array( $post_id, $request->get_params() );
182
183 return new WP_REST_Response( $this->prepare_campaign_response( get_post( $post_id ) ), 201 );
184 }
185
186 public function get_campaign( WP_REST_Request $request ): WP_REST_Response {
187 $post = $this->get_validated_post( $request['id'] );
188 if ( is_wp_error( $post ) ) {
189 return new WP_REST_Response( [ 'message' => $post->get_error_message() ], 404 );
190 }
191
192 return new WP_REST_Response( $this->prepare_campaign_response( $post ), 200 );
193 }
194
195 public function update_campaign( WP_REST_Request $request ): WP_REST_Response {
196 if ( ! $this->valid_nonce( $request ) ) {
197 return new WP_REST_Response( [ 'message' => 'Invalid nonce' ], 403 );
198 }
199
200 $post = $this->get_validated_post( $request['id'] );
201 if ( is_wp_error( $post ) ) {
202 return new WP_REST_Response( [ 'message' => $post->get_error_message() ], 404 );
203 }
204
205 $post_id = $post->ID;
206
207 // Update title / status if provided
208 $update = [ 'ID' => $post_id ];
209
210 if ( $request->get_param( 'title' ) ) {
211 $update['post_title'] = sanitize_text_field( $request->get_param( 'title' ) );
212 }
213
214 if ( $request->get_param( 'post_status' ) ) {
215 $update['post_status'] = sanitize_text_field( $request->get_param( 'post_status' ) );
216 }
217
218 if ( count( $update ) > 1 ) {
219 wp_update_post( $update );
220 }
221
222 MetaBox::save_from_array( $post_id, $request->get_params() );
223
224 // Bust stats cache in case goal changed.
225 CampaignStats::bust_cache( $post_id );
226
227 return new WP_REST_Response( $this->prepare_campaign_response( get_post( $post_id ) ), 200 );
228 }
229
230 public function delete_campaign( WP_REST_Request $request ): WP_REST_Response {
231 if ( ! $this->valid_nonce( $request ) ) {
232 return new WP_REST_Response( [ 'message' => 'Invalid nonce' ], 403 );
233 }
234
235 if ( ! current_user_can( 'manage_options' ) ) {
236 return new WP_REST_Response( [ 'message' => 'Forbidden' ], 403 );
237 }
238
239 $post = $this->get_validated_post( $request['id'] );
240 if ( is_wp_error( $post ) ) {
241 return new WP_REST_Response( [ 'message' => $post->get_error_message() ], 404 );
242 }
243
244 $force = filter_var( $request->get_param( 'force' ), FILTER_VALIDATE_BOOLEAN );
245
246 if ( $force ) {
247 wp_delete_post( $post->ID, true );
248 } else {
249 wp_trash_post( $post->ID );
250 }
251
252 return new WP_REST_Response( [ 'deleted' => true ], 200 );
253 }
254
255 public function restore_campaign( WP_REST_Request $request ): WP_REST_Response {
256 if ( ! $this->valid_nonce( $request ) ) {
257 return new WP_REST_Response( [ 'message' => 'Invalid nonce' ], 403 );
258 }
259
260 $post = get_post( (int) $request['id'] );
261 if ( ! $post || $post->post_type !== 'bp_campaign' ) {
262 return new WP_REST_Response( [ 'message' => 'Campaign not found' ], 404 );
263 }
264
265 wp_untrash_post( $post->ID );
266
267 return new WP_REST_Response( [ 'restored' => true ], 200 );
268 }
269
270 public function get_campaign_stats( WP_REST_Request $request ): WP_REST_Response {
271 $not_found = new WP_REST_Response( [ 'message' => 'Not found' ], 404 );
272
273 $post = $this->get_validated_post( $request['id'] );
274 if ( is_wp_error( $post ) ) {
275 return $not_found;
276 }
277
278 if ( $post->post_status !== 'publish' && ! current_user_can( 'manage_options' ) ) {
279 return $not_found;
280 }
281
282 $stats = CampaignStats::get_stats( $post->ID );
283
284 return new WP_REST_Response( $stats, 200 );
285 }
286
287 public function get_templates(): WP_REST_Response {
288 return new WP_REST_Response( array_values( TemplateManager::get_all() ), 200 );
289 }
290
291 public function preview_campaign( WP_REST_Request $request ): WP_REST_Response {
292 $post = $this->get_validated_post( $request['id'] );
293 if ( is_wp_error( $post ) ) {
294 return new WP_REST_Response( [ 'message' => $post->get_error_message() ], 404 );
295 }
296
297 $layout = $request->get_param( 'layout' );
298 if ( ! is_array( $layout ) ) {
299 return new WP_REST_Response( [ 'message' => 'Invalid layout' ], 400 );
300 }
301
302 $html = RendererService::render_campaign( $post->ID, true, $layout );
303
304 return new WP_REST_Response( [ 'html' => $html ], 200 );
305 }
306
307 public function preview_campaign_builder( WP_REST_Request $request ): WP_REST_Response {
308 $layout = $request->get_param( 'layout' );
309 $meta = $request->get_param( 'meta' ) ?: [];
310 $campaign_id = (int) ( $request->get_param( 'campaign_id' ) ?: 0 );
311
312 if ( ! is_array( $layout ) ) {
313 return new WP_REST_Response( [ 'message' => 'Invalid layout' ], 400 );
314 }
315
316 if ( ! is_array( $meta ) ) {
317 $meta = [];
318 }
319
320 $html = RendererService::build_preview_document( $layout, $meta, $campaign_id );
321 return new WP_REST_Response( [ 'html' => $html ], 200 );
322 }
323
324 public function preview_template( WP_REST_Request $request ): WP_REST_Response {
325 $key = sanitize_key( $request['key'] );
326 $html = RendererService::render_template_preview( $key );
327 if ( ! $html ) {
328 return new WP_REST_Response( [ 'message' => 'Template not found' ], 404 );
329 }
330 return new WP_REST_Response( [ 'html' => $html ], 200 );
331 }
332
333 // ------------------------------------------------------------------ helpers
334
335 public function check_admin_permissions() {
336 if ( ! current_user_can( 'manage_options' ) ) {
337 return new WP_Error( 'unauthorized', 'Unauthorized', [ 'status' => 401 ] );
338 }
339 return true;
340 }
341
342 private function valid_nonce( WP_REST_Request $request ): bool {
343 $nonce = $request->get_header( 'x_wp_nonce' );
344 return (bool) wp_verify_nonce( $nonce, 'wp_rest' );
345 }
346
347 private function get_validated_post( int $id ) {
348 $post = get_post( $id );
349 if ( ! $post || $post->post_type !== 'bp_campaign' ) {
350 return new WP_Error( 'not_found', __( 'Campaign not found', 'better-payment' ), [ 'status' => 404 ] );
351 }
352 return $post;
353 }
354
355 private function prepare_campaign_response( \WP_Post $post ): array {
356 $meta = MetaBox::get_all( $post->ID );
357 $stats = CampaignStats::get_stats( $post->ID );
358 $code = DB::get_settings( 'better_payment_settings_general_general_currency' );
359 $currency = ( is_string( $code ) && $code !== '' ) ? $code : 'USD';
360
361 return [
362 'id' => $post->ID,
363 'title' => $post->post_title,
364 'post_status' => $post->post_status,
365 'post_date' => $post->post_date,
366 'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'large' ) ?: '',
367 'edit_url' => admin_url( 'admin.php?page=bp-campaign-builder&campaign_id=' . $post->ID ),
368 'view_url' => $post->post_status === 'publish' ? ( get_permalink( $post->ID ) ?: '' ) : '',
369 'currency' => $currency,
370 'meta' => $meta,
371 'stats' => $stats,
372 ];
373 }
374 }
375