prepare()`. * * Pro keeps an identical copy in Models\AnalyticsModel (it supports a Lite older than this * method), pinned by Pro's AnalyticsModelParityTest. * * @since 2.4.0 * * @return string */ public static function approved_donation_sql(): string { return "( status IN ('" . implode( "', '", self::APPROVED_DONATION_STATUSES ) . "')" . " OR ( status = '" . self::LEGACY_FREE_CHECKOUT_STATUS . "' AND INSTR(COALESCE(form_fields_info, ''), '" . self::LEGACY_CONFIRMATION_MARKER . "') > 0 ) )"; } /** * PHP twin of `NOT COUNTED_ROW_SQL`, for rows already loaded: is this a legacy free checkout * that was never confirmed? * * A row without `form_fields_info` loaded is treated as unconfirmed (never counted), the safe * side of the rule. An already-unserialized `form_fields_info` array is read as the string it was * stored as, so a caller that unserialized the row first gets the same answer. * * @since 2.4.0 * * @param object|array $row A transaction row. * @return bool */ public static function is_unconfirmed_legacy_free_checkout( $row ): bool { return self::LEGACY_FREE_CHECKOUT_STATUS === self::row_value( $row, 'status' ) && false === strpos( self::form_fields_info_string( $row ), self::LEGACY_CONFIRMATION_MARKER ); } /** * The status bucket a loaded transaction row is labelled and listed under. * * The raw status's bucket (get_type_by_transaction_status()), except for an unconfirmed legacy * free checkout: no count or total includes it, so it is `incomplete` (v1: `unpaid`) rather than * the `completed` its `no_payment_required` status maps to. A confirmed one stays `completed`. * The row itself is never changed. Every PHP surface that labels a row reads this: the admin REST * rows (`status_type`), the CSV export, UserAPI, the User Dashboard template and the block's * editor rows. DB::get_transactions() applies the same rule to the Completed / Incomplete tabs. * * @since 2.4.0 * * @param object|array $row A transaction row. * @param string $version 'v2' (completed / incomplete / refunded) or 'v1'. * @return string The bucket key; '' for a status no bucket lists, as get_type_by_transaction_status(). */ public static function transaction_status_type( $row, string $version = 'v2' ): string { if ( self::is_unconfirmed_legacy_free_checkout( $row ) ) { return 'v1' === $version ? 'unpaid' : 'incomplete'; } return ( new self() )->get_type_by_transaction_status( self::row_value( $row, 'status' ), $version ); } /** * The status label of a loaded row ("Completed", "Incomplete", "Refunded"), v2 buckets. * * @since 2.4.0 * * @param object|array $row A transaction row. * @return string */ public static function transaction_status_label( $row ): string { return ucfirst( self::transaction_status_type( $row, 'v2' ) ); } /** * The status colour of a loaded row, v2 buckets: the colour of transaction_status_type()'s bucket. * * @since 2.4.0 * * @param object|array $row A transaction row. * @return string */ public static function transaction_status_color( $row ): string { $helper = new self(); if ( self::is_unconfirmed_legacy_free_checkout( $row ) ) { $buckets = $helper->transaction_statuses_with_type_v2(); return $buckets['incomplete']['color']; } return $helper->get_color_by_transaction_status( self::row_value( $row, 'status' ), 'v2' ); } /** * PHP twin of COUNTABLE_AMOUNT_SQL: what a loaded row contributes to a money total. * * @since 2.4.0 * * @param object|array $row A transaction row. * @return float */ public static function countable_amount( $row ): float { if ( self::LEGACY_FREE_CHECKOUT_STATUS === self::row_value( $row, 'status' ) ) { return 0.0; } $amount = self::row_value( $row, 'amount' ); return '' === $amount ? 0.0 : floatval( $amount ); } /** * Raised total and payment count over loaded transaction rows (the Elementor Fundraising * Campaign widget's progress). * * Counts the same rows CampaignStats counts: an approved status (Stripe `paid`, Paystack * `success`, `Completed`, …) or a confirmed legacy free checkout, which adds 0. It used to * count only `paid`, so Paystack and manually completed donations never moved the bar. * * @since 2.4.0 * * @param mixed $rows Transaction rows (objects or arrays). * @return array{amount: float, count: int} */ public static function campaign_totals_from_rows( $rows ): array { $totals = array( 'amount' => 0.0, 'count' => 0, ); foreach ( is_array( $rows ) ? $rows : array() as $row ) { $status = self::row_value( $row, 'status' ); $counted = in_array( $status, self::APPROVED_DONATION_STATUSES, true ) || ( self::LEGACY_FREE_CHECKOUT_STATUS === $status && ! self::is_unconfirmed_legacy_free_checkout( $row ) ); if ( ! $counted ) { continue; } $totals['amount'] += self::countable_amount( $row ); ++$totals['count']; } return $totals; } /** * A scalar column of a row object or array, as a string ('' when absent or not scalar). * * @param object|array $row A transaction row. * @param string $key Column name. * @return string */ private static function row_value( $row, string $key ): string { $value = null; if ( is_object( $row ) && isset( $row->$key ) ) { $value = $row->$key; } elseif ( is_array( $row ) && isset( $row[ $key ] ) ) { $value = $row[ $key ]; } return is_scalar( $value ) ? (string) $value : ''; } /** * A row's `form_fields_info` as stored: the serialized string, an already-unserialized array * serialized back, or '' when absent. Only ever searched for LEGACY_CONFIRMATION_MARKER. * * @param object|array $row A transaction row. * @return string */ private static function form_fields_info_string( $row ): string { $value = null; if ( is_object( $row ) && isset( $row->form_fields_info ) ) { $value = $row->form_fields_info; } elseif ( is_array( $row ) && isset( $row['form_fields_info'] ) ) { $value = $row['form_fields_info']; } if ( is_array( $value ) ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- searched for the marker, never unserialized. return serialize( $value ); } return is_scalar( $value ) ? (string) $value : ''; } // find_element_recursive() comes from Traits\Helper; this class used to carry // a byte-identical copy of it. public function titleToSnake($text, $divider = '_') { // Handle null or empty values to prevent deprecated warnings. if ( null === $text || '' === $text ) { return 'n_a'; } $text = (string) $text; $text = preg_replace('~[^\pL\d]+~u', $divider, $text); $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); $text = preg_replace('~[^-\w]+~', '', $text); $text = trim($text, $divider); $text = preg_replace('~-+~', $divider, $text); $text = strtolower($text); if (empty($text)) { return 'n_a'; } return $text; } public function transaction_statuses_with_type(){ $statuses = [ 'completed' => [ 'statuses' => [ 'paid', 'Completed', 'completed', // Stripe's own status for a fully-discounted / $0 Checkout // Session (100% coupon or sale, free trial). The payment // IS settled - there was simply nothing to charge - so it // must classify as completed, not fall through to the // unknown-status default. See Helper::get_type_by_transaction_status(). 'no_payment_required', ], 'color' => '#0ECA86', ], 'processing' => [ 'statuses' => [ 'pending', 'Pending', NULL, ], 'color' => '#FFDA15', ], 'refunded' => [ 'statuses' => [ 'refunded', ], 'color' => '#FF0202', ], 'unpaid' => [ 'statuses' => [ 'unpaid', // A payment that failed. 'failed', ], 'color' => '#999', ], 'all' => [ 'statuses' => [ 'paid', 'Completed', 'completed', 'no_payment_required', 'unpaid', 'refunded', 'pending', 'Pending', 'failed', NULL, ], 'color' => '#999', ], ]; return $statuses; } public function transaction_statuses_with_type_v2(){ $statuses = [ 'completed' => [ 'statuses' => [ 'paid', 'Completed', 'completed', 'success', // See the v1 note: a $0 Stripe session settles as // 'no_payment_required'. Every status bucket is also a // `status IN (...)` filter in DB::get_transactions(), so a // status missing from BOTH 'completed' and 'incomplete' // is invisible under either tab while still showing on // the unfiltered list - which is exactly how a paid $0 // order looked like a stray "Incomplete" row. 'no_payment_required', ], 'color' => '#0ECA86', ], 'incomplete' => [ 'statuses' => [ 'pending', 'Pending', 'unpaid', 'incomplete', 'Incomplete', // A payment that failed. 'failed', NULL, ], 'color' => '#FFDA15', ], 'refunded' => [ 'statuses' => [ 'refunded', ], 'color' => '#FF0202', ], 'all' => [ 'statuses' => [ 'paid', 'Completed', 'completed', 'no_payment_required', 'unpaid', 'refunded', 'pending', 'Pending', 'success', 'incomplete', 'Incomplete', 'failed', NULL, ], 'color' => '#999', ], ]; return $statuses; } public function get_type_by_transaction_status($status = '', $version = 'v1'){ if('v1' === $version){ $statuses = $this->transaction_statuses_with_type(); } else { $statuses = $this->transaction_statuses_with_type_v2(); } $status_type = ''; if(!empty($status)){ foreach($statuses as $type => $statuses_and_color){ if(in_array($status, $statuses_and_color['statuses'])){ $status_type = $type; break; } } } if($status === '' || $status === NULL){ $status_type = 'incomplete'; } return $status_type; } public function get_color_by_transaction_status($status = '', $version = 'v1'){ if('v1' === $version){ $statuses = $this->transaction_statuses_with_type(); } else { $statuses = $this->transaction_statuses_with_type_v2(); } $color = '#999'; if(!empty($status)){ foreach($statuses as $type => $statuses_and_color){ if(in_array($status, $statuses_and_color['statuses'])){ $color = $statuses_and_color['color']; break; } } } if($status === '' || $status === NULL){ $color = '#FFDA15'; //incomplete } return $color; } public function get_statuses_by_transaction_type($status_type = 'all', $version = 'v1'){ if('v1' === $version){ $statuses = $this->transaction_statuses_with_type(); } else { $statuses = $this->transaction_statuses_with_type_v2(); } $transaction_statuses = []; if(!empty($status_type)){ foreach($statuses as $type => $statuses_and_color){ if($type == $status_type){ $transaction_statuses = $statuses_and_color['statuses']; break; } } } return $transaction_statuses; } public function get_transaction_types($version = 'v1'){ if('v1' === $version){ $statuses = $this->transaction_statuses_with_type(); } else { $statuses = $this->transaction_statuses_with_type_v2(); } $statuses_types = array_keys($statuses); return $statuses_types; } /** * Get interval text for recurring and split payment * * @since 1.3.0 */ public function get_interval_text( $interval = '' ){ $interval_text = ''; switch( $interval ) { case 'day': $interval_text = 'Daily'; break; case 'month': case 'year': $interval_text = $interval . 'ly'; break; default : $interval_text = ! empty( $interval ) ? "Per " . esc_html( $interval ) : ""; break; } return esc_html( ucfirst( $interval_text ) ); } /** * The payment type label of a transaction, as the User Dashboard REST rows and * the CSV export print it. * * Lite's own checkouts only take one-time payments, so Lite says 'One Time'. * `better_payment/transaction/payment_type` ($label, $form_fields_info) relabels * a row: Pro returns 'Subscription' for its recurring and split payments. Lite's * copy of that (ProSubscriptionTransactionData::payment_type()) serves only an * active Pro that does not (<= 1.4.3). The CSV import reads the label back, so * keep these two strings. * * @since 2.4.0 * * @param mixed $form_fields_info The transaction's unserialized form_fields_info. * @return string */ public static function payment_type_label( $form_fields_info ): string { $form_fields_info = is_array( $form_fields_info ) ? $form_fields_info : []; $label = 'One Time'; if ( ProSubscriptionTransactionData::lite_copy_applies() ) { $label = ProSubscriptionTransactionData::payment_type( $label, $form_fields_info ); } $filtered = apply_filters( 'better_payment/transaction/payment_type', $label, $form_fields_info ); return is_string( $filtered ) ? $filtered : $label; } /** * Output the page header, compatible with both classic and block themes. * * Block themes don't use get_header() — they rely on block template parts. * We detect this with wp_is_block_theme() and render the header template * part via do_blocks() so it appears correctly on CPT single pages that * bypass the theme's block templates. * * @param string $block_header Pre-rendered header markup from do_blocks() (block themes only). * @return void */ public static function render_header( string $block_header = '' ): void { if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { ?> >
> last_query ); echo wp_kses_post( $wpdb->last_result ); echo wp_kses_post( $wpdb->last_error ); } echo '';
print_r( $data ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
if ( ! $print_only ) {
wp_die( 'Printed!' );
}
}
}