PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / gateways / paypal-commerce / class-charitable-paypal-cli.php

class-charitable-paypal-cli.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/gateways/paypal-commerce/class-charitable-paypal-cli.php

208 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Charitable PayPal Commerce — WP-CLI commands.
4 *
5 * Ships the escape-valve command for the PayPal Hard Fork (1.8.15):
6 *
7 * wp charitable paypal force-commerce-tier [--confirm-recurring-impact] [--dry-run]
8 *
9 * See PayPal-Gateway-Exclusivity-PrePlan.md, Section 5.4 (Surface 1) for the
10 * full behavior contract.
11 *
12 * @package Charitable/Gateways/PayPal Commerce
13 * @author WP Charitable
14 * @copyright Copyright (c) 2024-2026, WP Charitable LLC
15 * @license GPL-2.0+
16 * @since 1.8.11
17 */
18
19 // Exit if accessed directly.
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23
24 // Only register when WP-CLI is loaded.
25 if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
26 return;
27 }
28
29 if ( ! class_exists( 'Charitable_PayPal_CLI' ) ) :
30
31 /**
32 * Charitable PayPal Commerce CLI commands.
33 *
34 * @since 1.8.11
35 */
36 class Charitable_PayPal_CLI {
37
38 /**
39 * Force the locked PayPal user tier from `legacy` to `commerce`.
40 *
41 * Disables the legacy `paypal` gateway in `active_gateways`, flips the
42 * `charitable_paypal_user_tier` option to `commerce`, and writes a
43 * structured log entry. PayPal Commerce becomes visible in the gateway
44 * settings list immediately, but is NOT auto-enabled — the admin must
45 * complete the Connect with PayPal flow manually after the switch.
46 *
47 * Active legacy recurring donations are a hazard: PayPal will continue
48 * billing donors via IPN, but Charitable will not record those renewals
49 * after the legacy gateway is disabled. The command refuses to run when
50 * any are present unless `--confirm-recurring-impact` is passed.
51 *
52 * ## OPTIONS
53 *
54 * [--confirm-recurring-impact]
55 * : Acknowledge that PayPal will continue billing legacy recurring
56 * donors but Charitable will stop recording renewals.
57 *
58 * [--dry-run]
59 * : Print what would change without writing anything.
60 *
61 * ## EXAMPLES
62 *
63 * # Inspect impact without making changes.
64 * wp charitable paypal force-commerce-tier --dry-run
65 *
66 * # Switch a legacy site to PayPal Commerce, accepting recurring trade-off.
67 * wp charitable paypal force-commerce-tier --confirm-recurring-impact
68 *
69 * @subcommand force-commerce-tier
70 *
71 * @since 1.8.11
72 *
73 * @param array $args Positional arguments (unused).
74 * @param array $assoc_args Associative arguments.
75 * @return void
76 */
77 public function force_commerce_tier( $args, $assoc_args ) {
78 $tier = get_option( 'charitable_paypal_user_tier', 'commerce' );
79
80 if ( 'legacy' !== $tier ) {
81 WP_CLI::log(
82 sprintf(
83 /* translators: %s: current tier value */
84 __( 'No-op: the current PayPal tier is "%s". Nothing to switch.', 'charitable' ),
85 $tier
86 )
87 );
88 return;
89 }
90
91 $dry_run = ! empty( $assoc_args['dry-run'] );
92 $confirm_recurring = ! empty( $assoc_args['confirm-recurring-impact'] );
93 $active_recurring = $this->count_active_legacy_recurring();
94
95 if ( $active_recurring > 0 && ! $confirm_recurring ) {
96 WP_CLI::error(
97 sprintf(
98 /* translators: %d: number of active recurring donations */
99 __( 'You have %d active legacy PayPal recurring donations. PayPal will continue billing donors, but Charitable will not record those renewals as donations after this switch. Cancel them in PayPal first OR pass --confirm-recurring-impact to accept this trade-off.', 'charitable' ),
100 $active_recurring
101 )
102 );
103 return; // WP_CLI::error exits, but keep return for clarity in unit/test contexts.
104 }
105
106 if ( $dry_run ) {
107 WP_CLI::log( __( 'DRY RUN — nothing will be changed.', 'charitable' ) );
108 WP_CLI::log( sprintf( ' current tier: legacy' ) );
109 WP_CLI::log( sprintf( ' active legacy recurring donations: %d', $active_recurring ) );
110 WP_CLI::log( ' would: disable legacy paypal gateway' );
111 WP_CLI::log( ' would: set charitable_paypal_user_tier = commerce' );
112 WP_CLI::log( ' would: write paypal.tier.switched log entry' );
113 return;
114 }
115
116 // Disable the legacy `paypal` gateway in active_gateways. We replicate
117 // the body of the protected Charitable_Gateways::disable_gateway() rather
118 // than promoting it to public — option manipulation only, no side effects
119 // beyond the standard `charitable_gateway_disable` action.
120 $settings = get_option( 'charitable_settings', array() );
121 if ( isset( $settings['active_gateways']['paypal'] ) ) {
122 unset( $settings['active_gateways']['paypal'] );
123
124 if ( isset( $settings['default_gateway'] ) && 'paypal' === $settings['default_gateway'] ) {
125 $settings['default_gateway'] = ! empty( $settings['active_gateways'] )
126 ? key( $settings['active_gateways'] )
127 : '';
128 }
129
130 update_option( 'charitable_settings', $settings );
131
132 /** This action is documented in includes/gateways/class-charitable-gateways.php */
133 do_action( 'charitable_gateway_disable', 'paypal' );
134 }
135
136 update_option( 'charitable_paypal_user_tier', 'commerce' );
137
138 // Structured log entry (Q2 helper). The helper guards against writes on
139 // legacy-only sites where `paypal_commerce` is not yet active — but at
140 // this exact moment that's typically TRUE (we just disabled legacy and
141 // the admin hasn't run onboarding yet). Fall back to a direct write so
142 // the audit entry for tier.switched always lands.
143 $context = array(
144 'old_tier' => 'legacy',
145 'new_tier' => 'commerce',
146 'active_legacy_recurring_count' => $active_recurring,
147 'method' => 'wp_cli',
148 );
149
150 if ( class_exists( 'Charitable_PayPal_Logger' ) && Charitable_PayPal_Logger::should_log() ) {
151 Charitable_PayPal_Logger::info(
152 'paypal.tier.switched',
153 'PayPal user tier switched from legacy to commerce',
154 $context
155 );
156 } elseif ( function_exists( 'charitable_log_immediate' ) ) {
157 charitable_log_immediate(
158 '[paypal.tier.switched] PayPal user tier switched from legacy to commerce',
159 wp_json_encode( $context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ),
160 array(
161 'type' => 'gateway',
162 'level' => 'info',
163 'source' => 'paypal_commerce',
164 'object_type' => 'paypal_tier',
165 'force' => true,
166 )
167 );
168 }
169
170 WP_CLI::success(
171 sprintf(
172 /* translators: %s: gateway settings URL */
173 __( 'Tier switched to commerce. Connect PayPal Commerce: %s', 'charitable' ),
174 admin_url( 'admin.php?page=charitable-settings&tab=gateways&group=gateways_paypal_commerce' )
175 )
176 );
177 }
178
179 /**
180 * Count active legacy `paypal` recurring donations.
181 *
182 * Uses $wpdb directly because WP_Query / get_posts with `post_status => 'any'`
183 * silently excludes Charitable's custom `charitable-active` status.
184 *
185 * @since 1.8.11
186 *
187 * @return int
188 */
189 private function count_active_legacy_recurring() {
190 global $wpdb;
191
192 $count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
193 "SELECT COUNT(*) FROM {$wpdb->posts} p
194 JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
195 WHERE p.post_type = 'recurring_donation'
196 AND pm.meta_key = 'donation_gateway'
197 AND pm.meta_value = 'paypal'
198 AND p.post_status IN ( 'charitable-active', 'charitable-pending' )"
199 );
200
201 return (int) $count;
202 }
203 }
204
205 WP_CLI::add_command( 'charitable paypal', 'Charitable_PayPal_CLI' );
206
207 endif;
208