PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / includes / abilities / class-mainwp-abilities-clients.php

class-mainwp-abilities-clients.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at includes/abilities/class-mainwp-abilities-clients.php

1,243 lines 45.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Abilities - Clients
4 *
5 * @package MainWP\Dashboard
6 */
7
8 namespace MainWP\Dashboard;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 /**
15 * Class MainWP_Abilities_Clients
16 *
17 * Registers and implements client-related abilities for the MainWP Dashboard.
18 *
19 * This class provides 11 abilities:
20 * - mainwp/list-clients-v1: List MainWP clients with pagination and filtering
21 * - mainwp/count-clients-v1: Count total clients
22 * - mainwp/get-client-v1: Get detailed information about a single client
23 * - mainwp/add-client-v1: Create a new client
24 * - mainwp/update-client-v1: Update an existing client
25 * - mainwp/delete-client-v1: Delete a client (destructive operation)
26 * - mainwp/get-client-sites-v1: Get sites associated with a client
27 * - mainwp/count-client-sites-v1: Count sites associated with a client
28 * - mainwp/get-client-costs-v1: Get cost tracker entries for a client (feature-gated)
29 * - mainwp/suspend-client-v1: Suspend a client
30 * - mainwp/unsuspend-client-v1: Unsuspend a client
31 */
32 class MainWP_Abilities_Clients { //phpcs:ignore -- NOSONAR -- class complexity acceptable.
33
34 /**
35 * Register all client abilities.
36 *
37 * @return void
38 */
39 public static function register(): void {
40 if ( ! function_exists( 'wp_register_ability' ) ) {
41 return;
42 }
43
44 self::register_list_clients();
45 self::register_count_clients();
46 self::register_get_client();
47 self::register_add_client();
48 self::register_update_client();
49 self::register_delete_client();
50 self::register_get_client_sites();
51 self::register_count_client_sites();
52
53 // Feature-gated: Only register if Cost Tracker module is available.
54 if ( class_exists( 'MainWP\Dashboard\Module\CostTracker\Cost_Tracker_Manager' ) ) {
55 self::register_get_client_costs();
56 }
57
58 self::register_suspend_client();
59 self::register_unsuspend_client();
60 }
61
62 /**
63 * Register mainwp/list-clients-v1 ability.
64 *
65 * @return void
66 */
67 private static function register_list_clients(): void {
68 wp_register_ability(
69 'mainwp/list-clients-v1',
70 array(
71 'label' => __( 'List MainWP Clients', 'mainwp' ),
72 'description' => __( 'List MainWP clients with pagination and filtering. Possible errors: mainwp_invalid_input, ability_invalid_permissions', 'mainwp' ),
73 'category' => 'mainwp-clients',
74 'input_schema' => self::get_list_clients_input_schema(),
75 'output_schema' => self::get_list_clients_output_schema(),
76 'execute_callback' => array( self::class, 'execute_list_clients' ),
77 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
78 'meta' => array(
79 'show_in_rest' => true,
80 'annotations' => array(
81 'instructions' => '',
82 'readonly' => true,
83 'destructive' => false,
84 'idempotent' => true,
85 ),
86 ),
87 )
88 );
89 }
90
91 /**
92 * Get input schema for list-clients.
93 *
94 * @return array Input schema definition.
95 */
96 private static function get_list_clients_input_schema(): array {
97 return array(
98 'type' => array( 'object', 'null' ),
99 'properties' => array(
100 'page' => array(
101 'type' => 'integer',
102 'minimum' => 1,
103 'maximum' => 10000,
104 'default' => 1,
105 ),
106 'per_page' => array(
107 'type' => 'integer',
108 'minimum' => 1,
109 'maximum' => 100,
110 'default' => 20,
111 ),
112 'status' => array(
113 'type' => 'string',
114 'enum' => array( 'any', 'active', 'suspended' ),
115 'default' => 'any',
116 ),
117 'search' => array(
118 'type' => 'string',
119 'default' => '',
120 ),
121 'include' => array(
122 'type' => 'array',
123 'items' => array( 'type' => 'integer' ),
124 'default' => array(),
125 ),
126 'exclude' => array(
127 'type' => 'array',
128 'items' => array( 'type' => 'integer' ),
129 'default' => array(),
130 ),
131 ),
132 'default' => array(),
133 );
134 }
135
136 /**
137 * Get output schema for list-clients.
138 *
139 * @return array Output schema definition.
140 */
141 private static function get_list_clients_output_schema(): array {
142 return array(
143 'type' => 'object',
144 'properties' => array(
145 'items' => array(
146 'type' => 'array',
147 'items' => self::get_client_object_schema(),
148 ),
149 'page' => array( 'type' => 'integer' ),
150 'per_page' => array( 'type' => 'integer' ),
151 'total' => array( 'type' => 'integer' ),
152 ),
153 );
154 }
155
156 /**
157 * Execute list-clients ability.
158 *
159 * @param array $input Validated input parameters.
160 * @return array|\WP_Error Result array or error.
161 */
162 public static function execute_list_clients( array $input ) {
163 $input = MainWP_Abilities_Util::normalize_input(
164 $input,
165 array(
166 'page' => 1,
167 'per_page' => 20,
168 'status' => 'any',
169 'search' => '',
170 'include' => array(),
171 'exclude' => array(),
172 )
173 );
174
175 $args = array(
176 'page' => (int) $input['page'],
177 'per_page' => (int) $input['per_page'],
178 );
179
180 if ( 'active' === $input['status'] ) {
181 $args['suspended'] = 0;
182 } elseif ( 'suspended' === $input['status'] ) {
183 $args['suspended'] = 1;
184 }
185
186 if ( ! empty( $input['search'] ) ) {
187 $args['s'] = sanitize_text_field( trim( $input['search'] ) );
188 }
189
190 if ( ! empty( $input['include'] ) ) {
191 $args['include'] = $input['include'];
192 }
193
194 if ( ! empty( $input['exclude'] ) ) {
195 $args['exclude'] = $input['exclude'];
196 }
197
198 $db = MainWP_DB_Client::instance();
199 $clients = $db->get_wp_clients( $args );
200 $total = $db->get_wp_clients( array_merge( $args, array( 'count_only' => true ) ) );
201
202 $items = array();
203 if ( is_array( $clients ) ) {
204 foreach ( $clients as $client ) {
205 $items[] = MainWP_Abilities_Util::format_client_for_output( $client );
206 }
207 }
208
209 return array(
210 'items' => $items,
211 'page' => (int) $input['page'],
212 'per_page' => (int) $input['per_page'],
213 'total' => (int) $total,
214 );
215 }
216
217 /**
218 * Register mainwp/count-clients-v1 ability.
219 *
220 * @return void
221 */
222 private static function register_count_clients(): void {
223 wp_register_ability(
224 'mainwp/count-clients-v1',
225 array(
226 'label' => __( 'Count MainWP Clients', 'mainwp' ),
227 'description' => __( 'Count total number of MainWP clients. Possible errors: ability_invalid_permissions', 'mainwp' ),
228 'category' => 'mainwp-clients',
229 'input_schema' => self::get_count_clients_input_schema(),
230 'output_schema' => self::get_count_clients_output_schema(),
231 'execute_callback' => array( self::class, 'execute_count_clients' ),
232 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
233 'meta' => array(
234 'show_in_rest' => true,
235 'annotations' => array(
236 'instructions' => '',
237 'readonly' => true,
238 'destructive' => false,
239 'idempotent' => true,
240 ),
241 ),
242 )
243 );
244 }
245
246 /**
247 * Get input schema for count-clients.
248 *
249 * @return array Input schema definition.
250 */
251 private static function get_count_clients_input_schema(): array {
252 return array(
253 'type' => array( 'object', 'null' ),
254 'default' => array(),
255 );
256 }
257
258 /**
259 * Get output schema for count-clients.
260 *
261 * @return array Output schema definition.
262 */
263 private static function get_count_clients_output_schema(): array {
264 return array(
265 'type' => 'object',
266 'properties' => array(
267 'total' => array( 'type' => 'integer' ),
268 ),
269 );
270 }
271
272 /**
273 * Execute count-clients ability.
274 *
275 * @param array $input Validated input parameters (unused but required by ability signature).
276 * @return array|\WP_Error Result array or error.
277 */
278 public static function execute_count_clients( array $input ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- NOSONAR Required by ability signature.
279 $total = MainWP_DB_Client::instance()->get_wp_clients( array( 'count_only' => true ) );
280
281 return array(
282 'total' => (int) $total,
283 );
284 }
285
286 /**
287 * Register mainwp/get-client-v1 ability.
288 *
289 * @return void
290 */
291 private static function register_get_client(): void {
292 wp_register_ability(
293 'mainwp/get-client-v1',
294 array(
295 'label' => __( 'Get MainWP Client', 'mainwp' ),
296 'description' => __( 'Get detailed information about a single MainWP client. Possible errors: mainwp_client_not_found, ability_invalid_permissions', 'mainwp' ),
297 'category' => 'mainwp-clients',
298 'input_schema' => self::get_get_client_input_schema(),
299 'output_schema' => self::get_client_output_schema(),
300 'execute_callback' => array( self::class, 'execute_get_client' ),
301 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
302 'meta' => array(
303 'show_in_rest' => true,
304 'annotations' => array(
305 'instructions' => '',
306 'readonly' => true,
307 'destructive' => false,
308 'idempotent' => true,
309 ),
310 ),
311 )
312 );
313 }
314
315 /**
316 * Get input schema for get-client.
317 *
318 * @return array Input schema definition.
319 */
320 private static function get_get_client_input_schema(): array {
321 return array(
322 'type' => 'object',
323 'properties' => array(
324 'client_id_or_email' => array(
325 'type' => array( 'integer', 'string' ),
326 ),
327 ),
328 'required' => array( 'client_id_or_email' ),
329 );
330 }
331
332 /**
333 * Get output schema for single client.
334 *
335 * @return array Output schema definition.
336 */
337 private static function get_client_output_schema(): array {
338 return self::get_client_object_schema();
339 }
340
341 /**
342 * Execute get-client ability.
343 *
344 * @param array $input Validated input parameters.
345 * @return array|\WP_Error Result array or error.
346 */
347 public static function execute_get_client( array $input ) {
348 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
349
350 if ( is_wp_error( $client ) ) {
351 return $client;
352 }
353
354 return MainWP_Abilities_Util::format_client_for_output( $client );
355 }
356
357 /**
358 * Register mainwp/add-client-v1 ability.
359 *
360 * @return void
361 */
362 private static function register_add_client(): void {
363 wp_register_ability(
364 'mainwp/add-client-v1',
365 array(
366 'label' => __( 'Add MainWP Client', 'mainwp' ),
367 'description' => __( 'Create a new MainWP client. Possible errors: mainwp_invalid_input, ability_invalid_permissions', 'mainwp' ),
368 'category' => 'mainwp-clients',
369 'input_schema' => self::get_add_client_input_schema(),
370 'output_schema' => self::get_client_output_schema(),
371 'execute_callback' => array( self::class, 'execute_add_client' ),
372 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_manage_sites_permission' ),
373 'meta' => array(
374 'show_in_rest' => true,
375 'annotations' => array(
376 'instructions' => '',
377 'readonly' => false,
378 'destructive' => false,
379 'idempotent' => false,
380 ),
381 ),
382 )
383 );
384 }
385
386 /**
387 * Get input schema for add-client.
388 *
389 * @return array Input schema definition.
390 */
391 private static function get_add_client_input_schema(): array {
392 return array(
393 'type' => 'object',
394 'properties' => array(
395 'name' => array( 'type' => 'string' ),
396 'client_email' => array(
397 'type' => 'string',
398 'format' => 'email',
399 ),
400 'client_phone' => array( 'type' => 'string' ),
401 'address_1' => array( 'type' => 'string' ),
402 'address_2' => array( 'type' => 'string' ),
403 'city' => array( 'type' => 'string' ),
404 'state' => array( 'type' => 'string' ),
405 'zip' => array( 'type' => 'string' ),
406 'country' => array( 'type' => 'string' ),
407 'note' => array( 'type' => 'string' ),
408 'selected_sites' => array(
409 'type' => 'array',
410 'items' => array( 'type' => 'integer' ),
411 ),
412 'client_facebook' => array( 'type' => 'string' ),
413 'client_twitter' => array( 'type' => 'string' ),
414 'client_instagram' => array( 'type' => 'string' ),
415 'client_linkedin' => array( 'type' => 'string' ),
416 ),
417 'required' => array( 'name' ),
418 );
419 }
420
421 /**
422 * Execute add-client ability.
423 *
424 * @param array $input Validated input parameters.
425 * @return array|\WP_Error Result array or error.
426 */
427 public static function execute_add_client( array $input ) {
428 if ( empty( $input['name'] ) ) {
429 return new \WP_Error(
430 'mainwp_invalid_input',
431 __( 'Client name is required.', 'mainwp' ),
432 array( 'status' => 400 )
433 );
434 }
435
436 $result = MainWP_Client_Handler::rest_api_add_client( $input, false );
437
438 if ( is_wp_error( $result ) ) {
439 return $result;
440 }
441
442 if ( isset( $result['success'] ) && ! $result['success'] ) {
443 return new \WP_Error(
444 'mainwp_client_creation_failed',
445 isset( $result['error'] ) ? $result['error'] : __( 'Failed to create client.', 'mainwp' ),
446 array( 'status' => 500 )
447 );
448 }
449
450 $client_id = isset( $result['client_id'] ) ? (int) $result['client_id'] : 0;
451
452 if ( ! $client_id ) {
453 return new \WP_Error(
454 'mainwp_client_creation_failed',
455 __( 'Failed to create client.', 'mainwp' ),
456 array( 'status' => 500 )
457 );
458 }
459
460 $client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id );
461
462 if ( ! $client ) {
463 return new \WP_Error(
464 'mainwp_client_not_found',
465 __( 'Client was created but could not be retrieved.', 'mainwp' ),
466 array( 'status' => 500 )
467 );
468 }
469
470 return MainWP_Abilities_Util::format_client_for_output( $client );
471 }
472
473 /**
474 * Register mainwp/update-client-v1 ability.
475 *
476 * @return void
477 */
478 private static function register_update_client(): void {
479 wp_register_ability(
480 'mainwp/update-client-v1',
481 array(
482 'label' => __( 'Update MainWP Client', 'mainwp' ),
483 'description' => __( 'Update an existing MainWP client. Possible errors: mainwp_client_not_found, mainwp_invalid_input, ability_invalid_permissions', 'mainwp' ),
484 'category' => 'mainwp-clients',
485 'input_schema' => self::get_update_client_input_schema(),
486 'output_schema' => self::get_client_output_schema(),
487 'execute_callback' => array( self::class, 'execute_update_client' ),
488 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_manage_sites_permission' ),
489 'meta' => array(
490 'show_in_rest' => true,
491 'annotations' => array(
492 'instructions' => '',
493 'readonly' => false,
494 'destructive' => false,
495 'idempotent' => true,
496 ),
497 ),
498 )
499 );
500 }
501
502 /**
503 * Get input schema for update-client.
504 *
505 * @return array Input schema definition.
506 */
507 private static function get_update_client_input_schema(): array {
508 return array(
509 'type' => 'object',
510 'properties' => array(
511 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
512 'name' => array( 'type' => 'string' ),
513 'client_email' => array(
514 'type' => 'string',
515 'format' => 'email',
516 ),
517 'client_phone' => array( 'type' => 'string' ),
518 'address_1' => array( 'type' => 'string' ),
519 'address_2' => array( 'type' => 'string' ),
520 'city' => array( 'type' => 'string' ),
521 'state' => array( 'type' => 'string' ),
522 'zip' => array( 'type' => 'string' ),
523 'country' => array( 'type' => 'string' ),
524 'note' => array( 'type' => 'string' ),
525 'selected_sites' => array(
526 'type' => 'array',
527 'items' => array( 'type' => 'integer' ),
528 ),
529 'client_facebook' => array( 'type' => 'string' ),
530 'client_twitter' => array( 'type' => 'string' ),
531 'client_instagram' => array( 'type' => 'string' ),
532 'client_linkedin' => array( 'type' => 'string' ),
533 ),
534 'required' => array( 'client_id_or_email' ),
535 );
536 }
537
538 /**
539 * Execute update-client ability.
540 *
541 * @param array $input Validated input parameters.
542 * @return array|\WP_Error Result array or error.
543 */
544 public static function execute_update_client( array $input ) {
545 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
546
547 if ( is_wp_error( $client ) ) {
548 return $client;
549 }
550
551 $data = $input;
552 $data['client_id'] = $client->client_id;
553
554 unset( $data['client_id_or_email'] );
555
556 $result = MainWP_Client_Handler::rest_api_add_client( $data, true );
557
558 if ( is_wp_error( $result ) ) {
559 return $result;
560 }
561
562 if ( isset( $result['success'] ) && ! $result['success'] ) {
563 return new \WP_Error(
564 'mainwp_client_update_failed',
565 isset( $result['error'] ) ? $result['error'] : __( 'Failed to update client.', 'mainwp' ),
566 array( 'status' => 500 )
567 );
568 }
569
570 $updated_client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client->client_id );
571
572 if ( ! $updated_client ) {
573 return new \WP_Error(
574 'mainwp_client_not_found',
575 __( 'Client was updated but could not be retrieved.', 'mainwp' ),
576 array( 'status' => 500 )
577 );
578 }
579
580 return MainWP_Abilities_Util::format_client_for_output( $updated_client );
581 }
582
583 /**
584 * Register mainwp/delete-client-v1 ability.
585 *
586 * @return void
587 */
588 private static function register_delete_client(): void {
589 wp_register_ability(
590 'mainwp/delete-client-v1',
591 array(
592 'label' => __( 'Delete MainWP Client', 'mainwp' ),
593 'description' => __( 'Delete a MainWP client. Requires confirm: true or dry_run: true. Possible errors: mainwp_client_not_found, mainwp_confirmation_required, mainwp_invalid_input, ability_invalid_permissions', 'mainwp' ),
594 'category' => 'mainwp-clients',
595 'input_schema' => self::get_delete_client_input_schema(),
596 'output_schema' => self::get_delete_client_output_schema(),
597 'execute_callback' => array( self::class, 'execute_delete_client' ),
598 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_manage_sites_permission' ),
599 'meta' => array(
600 'show_in_rest' => true,
601 'annotations' => array(
602 'instructions' => 'Destructive operation requires confirm: true. Only call when user explicitly requests deletion.',
603 'readonly' => false,
604 'destructive' => true,
605 'idempotent' => false,
606 ),
607 ),
608 )
609 );
610 }
611
612 /**
613 * Get input schema for delete-client.
614 *
615 * @return array Input schema definition.
616 */
617 private static function get_delete_client_input_schema(): array {
618 return array(
619 'type' => 'object',
620 'properties' => array(
621 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
622 'confirm' => array(
623 'type' => 'boolean',
624 'default' => false,
625 ),
626 'dry_run' => array(
627 'type' => 'boolean',
628 'default' => false,
629 ),
630 ),
631 'required' => array( 'client_id_or_email' ),
632 );
633 }
634
635 /**
636 * Get output schema for delete-client.
637 *
638 * Uses required arrays to ensure oneOf branches are mutually exclusive:
639 * - Deletion response requires 'id' and 'name' (client properties)
640 * - Dry-run response requires 'dry_run' and 'would_affect'
641 *
642 * @return array Output schema definition.
643 */
644 private static function get_delete_client_output_schema(): array {
645 $client_schema = self::get_client_object_schema();
646 $client_schema['required'] = array( 'id', 'name' );
647
648 return array(
649 'oneOf' => array(
650 $client_schema,
651 array(
652 'type' => 'object',
653 'required' => array( 'dry_run', 'would_affect' ),
654 'properties' => array(
655 'dry_run' => array( 'type' => 'boolean' ),
656 'would_affect' => array(
657 'type' => 'object',
658 'properties' => array(
659 'client' => self::get_client_object_schema(),
660 'associated_sites_count' => array( 'type' => 'integer' ),
661 ),
662 ),
663 'count' => array( 'type' => 'integer' ),
664 'warnings' => array(
665 'type' => 'array',
666 'items' => array( 'type' => 'string' ),
667 ),
668 ),
669 ),
670 ),
671 );
672 }
673
674 /**
675 * Execute delete-client ability.
676 *
677 * @param array $input Validated input parameters.
678 * @return array|\WP_Error Result array or error.
679 */
680 public static function execute_delete_client( array $input ) {
681 $input = MainWP_Abilities_Util::normalize_input(
682 $input,
683 array(
684 'confirm' => false,
685 'dry_run' => false,
686 )
687 );
688
689 if ( $input['dry_run'] && $input['confirm'] ) {
690 return new \WP_Error(
691 'mainwp_invalid_input',
692 __( 'Cannot specify both dry_run and confirm. Use dry_run for preview, confirm for actual deletion.', 'mainwp' ),
693 array( 'status' => 400 )
694 );
695 }
696
697 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
698
699 if ( is_wp_error( $client ) ) {
700 return $client;
701 }
702
703 if ( $input['dry_run'] ) {
704 $sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $client->client_id );
705 $sites_count = is_array( $sites ) ? count( $sites ) : 0;
706
707 $warnings = array();
708 if ( $sites_count > 0 ) {
709 $warnings[] = sprintf(
710 /* translators: %d: number of sites */
711 _n(
712 'This client has %d associated site. Deleting the client will not delete the site.',
713 'This client has %d associated sites. Deleting the client will not delete the sites.',
714 $sites_count,
715 'mainwp'
716 ),
717 $sites_count
718 );
719 }
720
721 return array(
722 'dry_run' => true,
723 'would_affect' => array(
724 'client' => MainWP_Abilities_Util::format_client_for_output( $client ),
725 'associated_sites_count' => $sites_count,
726 ),
727 'count' => 1,
728 'warnings' => $warnings,
729 );
730 }
731
732 if ( ! $input['confirm'] ) {
733 return new \WP_Error(
734 'mainwp_confirmation_required',
735 __( 'This is a destructive operation. Set confirm: true or use dry_run: true for preview.', 'mainwp' ),
736 array( 'status' => 400 )
737 );
738 }
739
740 $formatted_client = MainWP_Abilities_Util::format_client_for_output( $client );
741
742 $deleted = MainWP_DB_Client::instance()->delete_client( $client->client_id );
743
744 if ( ! $deleted ) {
745 return new \WP_Error(
746 'mainwp_client_deletion_failed',
747 __( 'Failed to delete client.', 'mainwp' ),
748 array( 'status' => 500 )
749 );
750 }
751
752 return $formatted_client;
753 }
754
755 /**
756 * Register mainwp/get-client-sites-v1 ability.
757 *
758 * @return void
759 */
760 private static function register_get_client_sites(): void {
761 wp_register_ability(
762 'mainwp/get-client-sites-v1',
763 array(
764 'label' => __( 'Get Client Sites', 'mainwp' ),
765 'description' => __( 'Get sites associated with a MainWP client. Possible errors: mainwp_client_not_found, ability_invalid_permissions', 'mainwp' ),
766 'category' => 'mainwp-clients',
767 'input_schema' => self::get_count_client_sites_input_schema(),
768 'output_schema' => self::get_get_client_sites_output_schema(),
769 'execute_callback' => array( self::class, 'execute_get_client_sites' ),
770 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
771 'meta' => array(
772 'show_in_rest' => true,
773 'annotations' => array(
774 'instructions' => '',
775 'readonly' => true,
776 'destructive' => false,
777 'idempotent' => true,
778 ),
779 ),
780 )
781 );
782 }
783
784 /**
785 * Get output schema for get-client-sites.
786 *
787 * @return array Output schema definition.
788 */
789 private static function get_get_client_sites_output_schema(): array {
790 return array(
791 'type' => 'object',
792 'properties' => array(
793 'items' => array(
794 'type' => 'array',
795 'items' => array(
796 'type' => 'object',
797 'properties' => array(
798 'id' => array( 'type' => 'integer' ),
799 'url' => array( 'type' => 'string' ),
800 'name' => array( 'type' => 'string' ),
801 'status' => array( 'type' => 'string' ),
802 'client_id' => array( 'type' => array( 'integer', 'null' ) ),
803 ),
804 ),
805 ),
806 'total' => array( 'type' => 'integer' ),
807 ),
808 );
809 }
810
811 /**
812 * Execute get-client-sites ability.
813 *
814 * @param array $input Validated input parameters.
815 * @return array|\WP_Error Result array or error.
816 */
817 public static function execute_get_client_sites( array $input ) {
818 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
819
820 if ( is_wp_error( $client ) ) {
821 return $client;
822 }
823
824 $sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $client->client_id );
825
826 $items = array();
827 if ( is_array( $sites ) ) {
828 foreach ( $sites as $site ) {
829 $items[] = MainWP_Abilities_Util::format_site_for_output( $site );
830 }
831 }
832
833 return array(
834 'items' => $items,
835 'total' => count( $items ),
836 );
837 }
838
839 /**
840 * Register mainwp/count-client-sites-v1 ability.
841 *
842 * @return void
843 */
844 private static function register_count_client_sites(): void {
845 wp_register_ability(
846 'mainwp/count-client-sites-v1',
847 array(
848 'label' => __( 'Count Client Sites', 'mainwp' ),
849 'description' => __( 'Count sites associated with a MainWP client. Possible errors: mainwp_client_not_found, ability_invalid_permissions', 'mainwp' ),
850 'category' => 'mainwp-clients',
851 'input_schema' => self::get_count_client_sites_input_schema(),
852 'output_schema' => self::get_count_client_sites_output_schema(),
853 'execute_callback' => array( self::class, 'execute_count_client_sites' ),
854 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
855 'meta' => array(
856 'show_in_rest' => true,
857 'annotations' => array(
858 'instructions' => '',
859 'readonly' => true,
860 'destructive' => false,
861 'idempotent' => true,
862 ),
863 ),
864 )
865 );
866 }
867
868 /**
869 * Get input schema for count-client-sites.
870 *
871 * @return array Input schema definition.
872 */
873 private static function get_count_client_sites_input_schema(): array { // phpcs:ignore -- NOSONAR -- repeat function.
874 return array(
875 'type' => 'object',
876 'properties' => array(
877 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
878 ),
879 'required' => array( 'client_id_or_email' ),
880 );
881 }
882
883 /**
884 * Get output schema for count-client-sites.
885 *
886 * @return array Output schema definition.
887 */
888 private static function get_count_client_sites_output_schema(): array { // phpcs:ignore - NOSONAR -- same get_count_clients_output_schema.
889 return array(
890 'type' => 'object',
891 'properties' => array(
892 'total' => array( 'type' => 'integer' ),
893 ),
894 );
895 }
896
897 /**
898 * Execute count-client-sites ability.
899 *
900 * @param array $input Validated input parameters.
901 * @return array|\WP_Error Result array or error.
902 */
903 public static function execute_count_client_sites( array $input ) {
904 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
905
906 if ( is_wp_error( $client ) ) {
907 return $client;
908 }
909
910 $sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $client->client_id );
911 $count = is_array( $sites ) ? count( $sites ) : 0;
912
913 return array(
914 'total' => $count,
915 );
916 }
917
918 /**
919 * Register mainwp/get-client-costs-v1 ability.
920 *
921 * Feature-gated: Only registered if Cost Tracker module is available.
922 *
923 * @return void
924 */
925 private static function register_get_client_costs(): void {
926 wp_register_ability(
927 'mainwp/get-client-costs-v1',
928 array(
929 'label' => __( 'Get Client Costs', 'mainwp' ),
930 'description' => __( 'Get cost tracker entries for a MainWP client. Requires Cost Tracker module. Possible errors: mainwp_client_not_found, mainwp_module_not_available, ability_invalid_permissions', 'mainwp' ),
931 'category' => 'mainwp-clients',
932 'input_schema' => self::get_get_client_costs_input_schema(),
933 'output_schema' => self::get_get_client_costs_output_schema(),
934 'execute_callback' => array( self::class, 'execute_get_client_costs' ),
935 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_rest_api_permission' ),
936 'meta' => array(
937 'show_in_rest' => true,
938 'annotations' => array(
939 'instructions' => 'Requires Cost Tracker module',
940 'readonly' => true,
941 'destructive' => false,
942 'idempotent' => true,
943 ),
944 ),
945 )
946 );
947 }
948
949 /**
950 * Get input schema for get-client-costs.
951 *
952 * @return array Input schema definition.
953 */
954 private static function get_get_client_costs_input_schema(): array { // phpcs:ignore -- NOSONAR -- repeat function.
955 return array(
956 'type' => 'object',
957 'properties' => array(
958 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
959 ),
960 'required' => array( 'client_id_or_email' ),
961 );
962 }
963
964 /**
965 * Get output schema for get-client-costs.
966 *
967 * @return array Output schema definition.
968 */
969 private static function get_get_client_costs_output_schema(): array {
970 return array(
971 'type' => 'object',
972 'properties' => array(
973 'items' => array(
974 'type' => 'array',
975 'items' => array(
976 'type' => 'object',
977 'properties' => array(
978 'id' => array( 'type' => 'integer' ),
979 'name' => array( 'type' => 'string' ),
980 'type' => array( 'type' => 'string' ),
981 'price' => array( 'type' => 'number' ),
982 'renewal_type' => array( 'type' => 'string' ),
983 'payment_method' => array( 'type' => 'string' ),
984 'product_type' => array( 'type' => 'string' ),
985 'last_renewal' => array( 'type' => array( 'string', 'null' ) ),
986 'next_renewal' => array( 'type' => array( 'string', 'null' ) ),
987 ),
988 ),
989 ),
990 'total' => array( 'type' => 'integer' ),
991 ),
992 );
993 }
994
995 /**
996 * Execute get-client-costs ability.
997 *
998 * @param array $input Validated input parameters.
999 * @return array|\WP_Error Result array or error.
1000 */
1001 public static function execute_get_client_costs( array $input ) {
1002 if ( ! class_exists( 'MainWP\Dashboard\Module\CostTracker\Cost_Tracker_Manager' ) ) {
1003 return new \WP_Error(
1004 'mainwp_module_not_available',
1005 __( 'Cost Tracker module is not available.', 'mainwp' ),
1006 array( 'status' => 503 )
1007 );
1008 }
1009
1010 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
1011
1012 if ( is_wp_error( $client ) ) {
1013 return $client;
1014 }
1015
1016 if ( ! class_exists( 'MainWP\Dashboard\Module\CostTracker\Cost_Tracker_DB' ) ) {
1017 return new \WP_Error(
1018 'mainwp_internal_error',
1019 __( 'Cost Tracker database class is not available.', 'mainwp' ),
1020 array( 'status' => 500 )
1021 );
1022 }
1023
1024 $cost_db = \MainWP\Dashboard\Module\CostTracker\Cost_Tracker_DB::get_instance();
1025 $costs = $cost_db->get_all_cost_trackers_by_clients( array( $client->client_id ) );
1026
1027 $items = array();
1028 if ( is_array( $costs ) ) {
1029 foreach ( $costs as $cost ) {
1030 $items[] = MainWP_Abilities_Util::format_cost_for_output( $cost );
1031 }
1032 }
1033
1034 return array(
1035 'items' => $items,
1036 'total' => count( $items ),
1037 );
1038 }
1039
1040 /**
1041 * Register mainwp/suspend-client-v1 ability.
1042 *
1043 * @return void
1044 */
1045 private static function register_suspend_client(): void {
1046 wp_register_ability(
1047 'mainwp/suspend-client-v1',
1048 array(
1049 'label' => __( 'Suspend MainWP Client', 'mainwp' ),
1050 'description' => __( 'Suspend a MainWP client. Possible errors: mainwp_client_not_found, ability_invalid_permissions', 'mainwp' ),
1051 'category' => 'mainwp-clients',
1052 'input_schema' => self::get_suspend_client_input_schema(),
1053 'output_schema' => self::get_client_output_schema(),
1054 'execute_callback' => array( self::class, 'execute_suspend_client' ),
1055 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_manage_sites_permission' ),
1056 'meta' => array(
1057 'show_in_rest' => true,
1058 'annotations' => array(
1059 'instructions' => '',
1060 'readonly' => false,
1061 'destructive' => false,
1062 'idempotent' => true,
1063 ),
1064 ),
1065 )
1066 );
1067 }
1068
1069 /**
1070 * Get input schema for suspend-client.
1071 *
1072 * @return array Input schema definition.
1073 */
1074 private static function get_suspend_client_input_schema(): array { // phpcs:ignore -- NOSONAR -- repeat function.
1075 return array(
1076 'type' => 'object',
1077 'properties' => array(
1078 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
1079 ),
1080 'required' => array( 'client_id_or_email' ),
1081 );
1082 }
1083
1084 /**
1085 * Execute suspend-client ability.
1086 *
1087 * @param array $input Validated input parameters.
1088 * @return array|\WP_Error Result array or error.
1089 */
1090 public static function execute_suspend_client( array $input ) {
1091 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
1092
1093 if ( is_wp_error( $client ) ) {
1094 return $client;
1095 }
1096
1097 $updated = MainWP_DB_Client::instance()->update_client(
1098 array(
1099 'client_id' => $client->client_id,
1100 'suspended' => 1,
1101 )
1102 );
1103
1104 if ( ! $updated ) {
1105 return new \WP_Error(
1106 'mainwp_client_update_failed',
1107 __( 'Failed to suspend client.', 'mainwp' ),
1108 array( 'status' => 500 )
1109 );
1110 }
1111
1112 $updated_client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client->client_id );
1113
1114 if ( ! $updated_client ) {
1115 return new \WP_Error(
1116 'mainwp_client_not_found',
1117 __( 'Client was suspended but could not be retrieved.', 'mainwp' ),
1118 array( 'status' => 500 )
1119 );
1120 }
1121
1122 return MainWP_Abilities_Util::format_client_for_output( $updated_client );
1123 }
1124
1125 /**
1126 * Register mainwp/unsuspend-client-v1 ability.
1127 *
1128 * @return void
1129 */
1130 private static function register_unsuspend_client(): void {
1131 wp_register_ability(
1132 'mainwp/unsuspend-client-v1',
1133 array(
1134 'label' => __( 'Unsuspend MainWP Client', 'mainwp' ),
1135 'description' => __( 'Unsuspend a MainWP client. Possible errors: mainwp_client_not_found, ability_invalid_permissions', 'mainwp' ),
1136 'category' => 'mainwp-clients',
1137 'input_schema' => self::get_unsuspend_client_input_schema(),
1138 'output_schema' => self::get_client_output_schema(),
1139 'execute_callback' => array( self::class, 'execute_unsuspend_client' ),
1140 'permission_callback' => array( MainWP_Abilities_Util::class, 'check_manage_sites_permission' ),
1141 'meta' => array(
1142 'show_in_rest' => true,
1143 'annotations' => array(
1144 'instructions' => '',
1145 'readonly' => false,
1146 'destructive' => false,
1147 'idempotent' => true,
1148 ),
1149 ),
1150 )
1151 );
1152 }
1153
1154 /**
1155 * Get input schema for unsuspend-client.
1156 *
1157 * @return array Input schema definition.
1158 */
1159 private static function get_unsuspend_client_input_schema(): array { // phpcs:ignore -- NOSONAR -- repeat function.
1160 return array(
1161 'type' => 'object',
1162 'properties' => array(
1163 'client_id_or_email' => array( 'type' => array( 'integer', 'string' ) ),
1164 ),
1165 'required' => array( 'client_id_or_email' ),
1166 );
1167 }
1168
1169 /**
1170 * Execute unsuspend-client ability.
1171 *
1172 * @param array $input Validated input parameters.
1173 * @return array|\WP_Error Result array or error.
1174 */
1175 public static function execute_unsuspend_client( array $input ) {
1176 $client = MainWP_Abilities_Util::resolve_client( $input['client_id_or_email'] );
1177
1178 if ( is_wp_error( $client ) ) {
1179 return $client;
1180 }
1181
1182 $updated = MainWP_DB_Client::instance()->update_client(
1183 array(
1184 'client_id' => $client->client_id,
1185 'suspended' => 0,
1186 )
1187 );
1188
1189 if ( ! $updated ) {
1190 return new \WP_Error(
1191 'mainwp_client_update_failed',
1192 __( 'Failed to unsuspend client.', 'mainwp' ),
1193 array( 'status' => 500 )
1194 );
1195 }
1196
1197 $updated_client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client->client_id );
1198
1199 if ( ! $updated_client ) {
1200 return new \WP_Error(
1201 'mainwp_client_not_found',
1202 __( 'Client was unsuspended but could not be retrieved.', 'mainwp' ),
1203 array( 'status' => 500 )
1204 );
1205 }
1206
1207 return MainWP_Abilities_Util::format_client_for_output( $updated_client );
1208 }
1209
1210 /**
1211 * Get client object schema.
1212 *
1213 * @return array Client object schema definition.
1214 */
1215 private static function get_client_object_schema(): array {
1216 return array(
1217 'type' => 'object',
1218 'properties' => array(
1219 'id' => array( 'type' => 'integer' ),
1220 'name' => array( 'type' => 'string' ),
1221 'email' => array( 'type' => 'string' ),
1222 'phone' => array( 'type' => 'string' ),
1223 'address_1' => array( 'type' => 'string' ),
1224 'address_2' => array( 'type' => 'string' ),
1225 'city' => array( 'type' => 'string' ),
1226 'state' => array( 'type' => 'string' ),
1227 'zip' => array( 'type' => 'string' ),
1228 'country' => array( 'type' => 'string' ),
1229 'note' => array( 'type' => 'string' ),
1230 'suspended' => array( 'type' => 'integer' ),
1231 'created' => array(
1232 'type' => array( 'string', 'null' ),
1233 'format' => 'date-time',
1234 ),
1235 'facebook' => array( 'type' => 'string' ),
1236 'twitter' => array( 'type' => 'string' ),
1237 'instagram' => array( 'type' => 'string' ),
1238 'linkedin' => array( 'type' => 'string' ),
1239 ),
1240 );
1241 }
1242 }
1243