diff --git a/assets/src/js/frontend/give-donations.js b/assets/src/js/frontend/give-donations.js index a43e6d3642..a481b40760 100644 --- a/assets/src/js/frontend/give-donations.js +++ b/assets/src/js/frontend/give-donations.js @@ -416,8 +416,18 @@ jQuery( function( $ ) { // Auto set give price id. $( 'input[name="give-price-id"]', parent_form ).val( price_id ); - // Update hidden amount field - parent_form.find( '.give-amount-hidden' ).val( Give.form.fn.formatAmount( value_now, parent_form, {} ) ); + // Update hidden amount field + const hiddenAmountField = parent_form.find('.give-amount-hidden'); + if (hiddenAmountField) { + hiddenAmountField.val(Give.form.fn.formatAmount(value_now, parent_form, {})); + // Trigger change event. + // We use amount field classes to trigger change event on input field, + // But when custom amount disabled then we use hidden field to store amount and span HTML tag to show amount. + // This means, if logic depends on amount change event (field with "give-amount" name) then it will not work. + // So, it is required to trigger change event on 'give-amount' hidden field. + // For example: Form field manager (feature: conditional field visibility) + hiddenAmountField.trigger('change'); + } // Remove old selected class & add class for CSS purposes parent_form.find( '.give-default-level' ).removeClass( 'give-default-level' ); diff --git a/give.php b/give.php index 0db6335baa..81734b95c9 100644 --- a/give.php +++ b/give.php @@ -6,7 +6,7 @@ * Description: The most robust, flexible, and intuitive way to accept donations on WordPress. * Author: GiveWP * Author URI: https://givewp.com/ - * Version: 2.25.3 + * Version: 2.26.0 * Requires at least: 5.0 * Requires PHP: 7.0 * Text Domain: give @@ -56,6 +56,7 @@ use Give\Form\LegacyConsumer\ServiceProvider as FormLegacyConsumerServiceProvider; use Give\Form\Templates; use Give\Framework\Database\ServiceProvider as DatabaseServiceProvider; +use Give\Framework\DesignSystem\DesignSystemServiceProvider; use Give\Framework\Exceptions\Primitives\InvalidArgumentException; use Give\Framework\Exceptions\UncaughtExceptionLogger; use Give\Framework\Http\ServiceProvider as HttpServiceProvider; @@ -209,6 +210,7 @@ final class Give ValidationServiceProvider::class, ValidationRulesServiceProvider::class, HttpServiceProvider::class, + DesignSystemServiceProvider::class, ]; /** @@ -312,7 +314,7 @@ private function setup_constants() { // Plugin version. if (!defined('GIVE_VERSION')) { - define('GIVE_VERSION', '2.25.2'); + define('GIVE_VERSION', '2.26.0'); } // Plugin Root File. diff --git a/includes/admin/admin-actions.php b/includes/admin/admin-actions.php index 8cd4bfa436..484b1c981b 100644 --- a/includes/admin/admin-actions.php +++ b/includes/admin/admin-actions.php @@ -709,7 +709,10 @@ function give_donation_import_callback() { $import_setting['dry_run'] = $output['dry_run']; // Parent key id. - $main_key = maybe_unserialize( $output['main_key'] ); + $main_key = is_serialized( $output['main_key'] ) + /** @since 2.26.0 Avoid insecure usage of `unserialize` when the data could be submitted by the user. */ + ? @unserialize( trim( $output['main_key'] ), ['allowed_classes' => false] ) + : $output['main_key']; $current = absint( $_REQUEST['current'] ); $total_ajax = absint( $_REQUEST['total_ajax'] ); diff --git a/includes/admin/import-functions.php b/includes/admin/import-functions.php index 65fd56d8d2..2b4da8d9c0 100644 --- a/includes/admin/import-functions.php +++ b/includes/admin/import-functions.php @@ -45,7 +45,8 @@ function give_import_donation_report_reset() { /** * Give get form data from csv if not then create and form and return the form value. * - * @since 1.8.13. + * @since 1.8.13. + * @since 2.26.0 Replace deprecated get_page_by_title() with give_get_page_by_title(). * * @param $data . * @@ -73,7 +74,7 @@ function give_import_get_form_data_from_csv( $data, $import_setting = [] ) { } if ( false === $form && ! empty( $data['form_title'] ) ) { - $form = get_page_by_title( $data['form_title'], OBJECT, 'give_forms' ); + $form = give_get_page_by_title($data['form_title'], OBJECT, 'give_forms'); if ( ! empty( $form->ID ) ) { @@ -96,7 +97,7 @@ function give_import_get_form_data_from_csv( $data, $import_setting = [] ) { } - $form = get_page_by_title( $data['form_title'], OBJECT, 'give_forms' ); + $form = give_get_page_by_title($data['form_title'], OBJECT, 'give_forms'); if ( ! empty( $form->ID ) ) { $form = new Give_Donate_Form( $form->ID ); } diff --git a/includes/admin/tools/import/class-give-import-core-settings.php b/includes/admin/tools/import/class-give-import-core-settings.php index 21202441f2..f5cde343b0 100644 --- a/includes/admin/tools/import/class-give-import-core-settings.php +++ b/includes/admin/tools/import/class-give-import-core-settings.php @@ -485,6 +485,7 @@ public static function upload_widget_settings_file() { public static function json_upload_mimes( $existing_mimes = array() ) { $existing_mimes['json'] = 'application/json'; + $existing_mimes['text'] = 'text/plain'; return $existing_mimes; } diff --git a/includes/misc-functions.php b/includes/misc-functions.php index d3460e69f3..e8a4a9773a 100644 --- a/includes/misc-functions.php +++ b/includes/misc-functions.php @@ -2578,3 +2578,37 @@ function give_check_addon_updates( $_transient_data ) { return $_transient_data; } + +/** + * Get page by title + * + * @since 2.26.0 + * + * @param string $page_title + * @param string $output + * @param string $post_type + * + * @return null|WP_Post + */ +function give_get_page_by_title($page_title, $output = OBJECT, $post_type) +{ + $args = [ + 'title' => $page_title, + 'post_type' => $post_type, + 'post_status' => get_post_stati(), + 'posts_per_page' => 1, + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'no_found_rows' => true, + 'orderby' => 'post_date ID', + 'order' => 'ASC', + ]; + $query = new WP_Query($args); + $pages = $query->posts; + + if (empty($pages)) { + return null; + } + + return get_post($pages[0], $output); +} diff --git a/package-lock.json b/package-lock.json index 9aa39323f2..fe2ced5231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.12", + "@givewp/design-system-foundation": "^1.1.0", "@paypal/paypal-js": "^1.0.2", "@reach/tabs": "^0.16.1", "@stripe/react-stripe-js": "^1.2.2", @@ -2230,6 +2231,11 @@ "react": ">=16.x" } }, + "node_modules/@givewp/design-system-foundation": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@givewp/design-system-foundation/-/design-system-foundation-1.1.0.tgz", + "integrity": "sha512-SOAS98QQOytIGsyDX55y4TCS0DeKijjmOPnNaG0YbClTL2u7HFNthqRHk246BXZ0s6U+CUzqZQ8mf/+3NY4Z1g==" + }, "node_modules/@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -27036,6 +27042,11 @@ "prop-types": "^15.7.2" } }, + "@givewp/design-system-foundation": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@givewp/design-system-foundation/-/design-system-foundation-1.1.0.tgz", + "integrity": "sha512-SOAS98QQOytIGsyDX55y4TCS0DeKijjmOPnNaG0YbClTL2u7HFNthqRHk246BXZ0s6U+CUzqZQ8mf/+3NY4Z1g==" + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", diff --git a/package.json b/package.json index 8061db8457..de8ea96f90 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.12", + "@givewp/design-system-foundation": "^1.1.0", "@paypal/paypal-js": "^1.0.2", "@reach/tabs": "^0.16.1", "@stripe/react-stripe-js": "^1.2.2", diff --git a/readme.txt b/readme.txt index e2a6980fbc..ef3f9bba1a 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: givewp, dlocc, webdevmattcrom, ravinderk, mehul0810, kevinwhoffman Donate link: https://go.givewp.com/home Tags: donation, donate, recurring donations, fundraising, crowdfunding Requires at least: 5.0 -Tested up to: 6.1 +Tested up to: 6.2 Requires PHP: 7.0 -Stable tag: 2.25.3 +Stable tag: 2.26.0 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl-3.0.html @@ -128,6 +128,13 @@ Are you a developer? GiveWP is built with best practices and easy to extend and * [Site Redesigns Without Donation Data Loss](https://go.givewp.com/datalossdoc) * [Handling Custom CSS in WordPress](https://go.givewp.com/cssdoc) +=== 🚀 Join the Journey to Create the Next Generation of WordPress Donation Forms === +Team Give has been working hard for the past several years on updating how donation forms are created. The user experience is going to change for the better, but we want your help shaping what that means! + +Help us test our new visual form builder with the GiveWP 3.0 Feature plugin. The Feaure Plugin (or GiveWP 3.0 Beta) is meant to be used alongside GiveWP core on a staging or local environment. We are looking specifically at the form builder with this beta test and would love for all GiveWP users to give it a try. All feedback is welcome! [Download the beta plugin directly on WordPress](https://go.givewp.com/corewppg) or through your admin dashboard plugins area. + +Learn more about how we're creating the next generation of WordPress donation forms, [directly on our website](https://go.givewp.com/corenextgen). + === 💚 About the GiveWP Team === GiveWP is part of StellarWP, a Liquid Web Family Brand. Our donation plugin is backed by a growing team of WordPress developers, support engineers, customer success managers, and marketing professionals who’ve worked with WordPress and nonprofits since 2009. This means GiveWP is made with best practices in mind; extremely extensible and customizable, stable, and reliable. We’ll be here in years to come for you and your nonprofit organization. @@ -144,11 +151,11 @@ Stay in touch with us for important plugin news and updates: === 🐱‍💻 Contribute to GiveWP === -This plugin is proudly open source (GPL license) and we’re always looking for more contributors. Whether you know another language, can code like no one’s business, or just have an idea, we would love your help and input. +This plugin is proudly open source (GPL license) and we’re always looking for more contributors. Whether you know another language, love to code, or just have an idea, we would love your help and input. Here’s a few ways you can contribute to GiveWP: -* Star/fork/watch the [GiveWP GitHub repository](https://go.givewp.com/github "Visit the GiveWP GitHub Repo") to learn more about what issues we're tackling and the project is developing. If you've never worked with Github before, learn about [pull requests here](https://help.github.com/articles/about-pull-requests/) and submit one for GiveWP, we'd love to give you our feedback. +* Star/fork/watch the [GiveWP GitHub repository](https://go.givewp.com/github) to learn more about what issues we're tackling and the project is developing. If you've never worked with Github before, learn about [pull requests here](https://help.github.com/articles/about-pull-requests/) and submit one for GiveWP, we'd love to give you our feedback. * Translate GiveWP into your native language. The best place to do that is here on wordpress.org. Go to [https://translate.wordpress.org/](https://translate.wordpress.org/projects/wp-plugins/give), then search for your language, click the "Plugins" tab, then search for "GiveWP". When you've submitted at least 95% of GiveWP's strings, the language moderators will review and approve your translations and then they will be available to all WordPress users for your native language. If you are interested in translating any of our Premium Add-ons, [contact us](https://go.givewp.com/contact), we'd love to chat with you about that. @@ -251,6 +258,15 @@ The 2% fee on Stripe donations only applies to donations taken via our free Stri 8. GiveWP has a dedicated support team to help answer any questions you may have and help you through stumbling blocks. == Changelog == += 2.26.0: April 6th, 2023 = +* Enhancement: Minor updates for improved WordPress 6.2 compatibility +* Enhancement: A number of under the hood improvements in preparation for the upcoming Visual Donation Form Builder feature plugin release +* Enhancement: Improvements to recurring donations in the Gateway API +* Enhancement: Implemented our new GiveWP design system to improve designs across our website and prdocuts +* Fix: Conditionals fields based on the amount field work again +* Fix: Files with a text mime type now work when uploading files for import +* Fix: If an error occurs in the Donor Dashboard when canceling a subscription, that subscription is no longer marked as canceled + = 2.25.3: March 22nd, 2023 = * Security: Protect against CSRF during donation import diff --git a/src/DonorDashboards/Tabs/Contracts/Route.php b/src/DonorDashboards/Tabs/Contracts/Route.php index 14a23c3214..23eacf061c 100644 --- a/src/DonorDashboards/Tabs/Contracts/Route.php +++ b/src/DonorDashboards/Tabs/Contracts/Route.php @@ -2,9 +2,11 @@ namespace Give\DonorDashboards\Tabs\Contracts; +use Exception; use Give\API\RestRoute; use Give\DonorDashboards\Helpers as DonorDashboardHelpers; use Give\Log\Log; +use WP_Error; use WP_REST_Request; use WP_REST_Response; @@ -69,6 +71,9 @@ public function registerRoute() ); } + /** + * @since 2.26.0 add try/catch to handleRequest + */ public function handleRequestWithDonorIdCheck(WP_REST_Request $request) { // Check that the provided donor ID is valid @@ -102,6 +107,14 @@ public function handleRequestWithDonorIdCheck(WP_REST_Request $request) ); } - return $this->handleRequest($request); + try { + $response = $this->handleRequest($request); + + return rest_ensure_response($response ?? []); + } catch (Exception $exception) { + $error = new WP_Error('error', $exception->getMessage()); + + return rest_ensure_response($error); + } } } diff --git a/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/index.js b/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/index.js index d2ee97c039..0da129aa91 100644 --- a/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/index.js +++ b/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/index.js @@ -3,10 +3,34 @@ import {cancelSubscriptionWithAPI} from './utils'; import {__} from '@wordpress/i18n'; import './style.scss'; +import {useState} from 'react'; + +const responseIsError = (response) => { + return response?.data?.code.includes('error'); +}; + +const getErrorMessageFromResponse = (response) => { + if (response?.data?.code === 'internal_server_error' || !response?.data?.message) { + return __('An error occurred while cancelling your subscription.', 'give'); + } + + return response?.data?.message; +}; const SubscriptionCancelModal = ({id, onRequestClose}) => { + const [cancelling, setCancelling] = useState(false); const handleCancel = async () => { - await cancelSubscriptionWithAPI(id); + setCancelling(true); + const response = await cancelSubscriptionWithAPI(id); + + if (responseIsError(response)) { + const errorMessage = getErrorMessageFromResponse(response); + + window.alert(errorMessage); + } + + setCancelling(false); + onRequestClose(); }; @@ -16,7 +40,9 @@ const SubscriptionCancelModal = ({id, onRequestClose}) => {
{__('Cancel Subscription?', 'give')}
- + onRequestClose()}> {__('Nevermind', 'give')} diff --git a/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/utils/index.js b/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/utils/index.js index 00b7428d60..9836942f30 100644 --- a/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/utils/index.js +++ b/src/DonorDashboards/resources/js/app/components/subscription-cancel-modal/utils/index.js @@ -1,17 +1,20 @@ import {donorDashboardApi} from '../../../utils'; import {fetchSubscriptionsDataFromAPI} from '../../../tabs/recurring-donations/utils'; -export const cancelSubscriptionWithAPI = (id) => { - return donorDashboardApi - .post( +export const cancelSubscriptionWithAPI = async (id) => { + try { + const response = await donorDashboardApi.post( 'recurring-donations/subscription/cancel', { id: id, }, {} - ) - .then(async (response) => { - await fetchSubscriptionsDataFromAPI(); - return response; - }); + ); + + await fetchSubscriptionsDataFromAPI(); + + return await response; + } catch (error) { + return error.response; + } }; diff --git a/src/Framework/DesignSystem/Actions/RegisterDesignSystemStyles.php b/src/Framework/DesignSystem/Actions/RegisterDesignSystemStyles.php new file mode 100644 index 0000000000..ef673e5b3b --- /dev/null +++ b/src/Framework/DesignSystem/Actions/RegisterDesignSystemStyles.php @@ -0,0 +1,21 @@ +getMessage(), [ 'Payment Gateway' => static::id(), - 'Donation' => $donation, + 'Donation' => $donation->toArray(), ] ); @@ -150,8 +150,8 @@ public function handleCreateSubscription(Donation $donation, Subscription $subsc $exception->getMessage(), [ 'Payment Gateway' => static::id(), - 'Donation' => $donation, - 'Subscription' => $subscription, + 'Donation' => $donation->toArray(), + 'Subscription' => $subscription->toArray(), ] ); @@ -352,6 +352,7 @@ public function handleGatewayPaymentCommand(GatewayCommand $command, Donation $d /** * Handle gateway subscription command * + * @since 2.26.0 add RespondToBrowser command * @since 2.21.0 Handle RedirectOffsite response. * @since 2.18.0 * @@ -390,6 +391,12 @@ public function handleGatewaySubscriptionCommand( $this->handleResponse($response); } + if ($command instanceof RespondToBrowser) { + $response = (new RespondToBrowserHandler())($command); + + $this->handleResponse($response); + } + throw new TypeNotSupported( sprintf( "Return type must be an instance of %s", diff --git a/src/LegacySubscriptions/includes/give-subscriptions-db.php b/src/LegacySubscriptions/includes/give-subscriptions-db.php index 2a2c391bc6..525383adad 100644 --- a/src/LegacySubscriptions/includes/give-subscriptions-db.php +++ b/src/LegacySubscriptions/includes/give-subscriptions-db.php @@ -600,6 +600,8 @@ private function generate_groupby_clause($groupby = '') } /** + * @since 2.26.0 Replace deprecated get_page_by_title() with give_get_page_by_title(). + * * @param $args * * @return string @@ -633,7 +635,7 @@ private function mysql_where_args_search($args) $where .= " AND `id` = " . absint($args['search']) . ""; } else { // See if search matches a product name - $form = get_page_by_title(trim($args['search']), OBJECT, 'give_forms'); + $form = give_get_page_by_title(trim($args['search']), OBJECT, 'give_forms'); if ($form) { $args['search'] = $form->ID; $where .= " AND `product_id` = " . absint($args['search']) . ""; diff --git a/src/PaymentGateways/Actions/RegisterPaymentGateways.php b/src/PaymentGateways/Actions/RegisterPaymentGateways.php index 383c720fb0..1c792a071b 100644 --- a/src/PaymentGateways/Actions/RegisterPaymentGateways.php +++ b/src/PaymentGateways/Actions/RegisterPaymentGateways.php @@ -14,7 +14,12 @@ use Give\PaymentGateways\Gateways\Stripe\CreditCardGateway as StripeCreditCardGateway; use Give\PaymentGateways\Gateways\Stripe\SEPAGateway as StripeSEPAGateway; use Give\PaymentGateways\PayPalCommerce\Actions\GetPayPalOrderFromRequest; +use Give\PaymentGateways\PayPalCommerce\Exceptions\PayPalOrderException; +use Give\PaymentGateways\PayPalCommerce\Exceptions\PayPalOrderIdException; use Give\PaymentGateways\PayPalCommerce\PayPalCommerce; +use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalOrder; +use PayPalHttp\HttpException; +use PayPalHttp\IOException; class RegisterPaymentGateways { @@ -130,7 +135,11 @@ function ($gatewayData, Donation $donation) { } /** + * @since 2.26.0 Add support for the updated PayPal Commerce gateway data. * @since 2.21.2 + * + * @throws PayPalOrderIdException + * @throws PayPalOrderException */ private function addGatewayDataToPayPalCommerce() { @@ -140,7 +149,18 @@ private function addGatewayDataToPayPalCommerce() PayPalCommerce::id() ), function ($gatewayData) { - $gatewayData['paypalOrder'] = (new GetPayPalOrderFromRequest())(); + $paypalOrderId = $gatewayData['payPalOrderId'] ?? give_clean($_POST['payPalOrderId']); + + if( ! $paypalOrderId ) { + throw new PayPalOrderIdException(__('PayPal order id is missing.', 'give')); + } + + try { + $gatewayData['paypalOrder'] = give(PayPalOrder::class)->getOrder($paypalOrderId); + } catch (\Exception $e) { + throw new PayPalOrderException(__('Unable to get order using order id.', 'give')); + } + return $gatewayData; } ); diff --git a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/ChargeRefunded.php b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/ChargeRefunded.php index fd9f3936f6..5306359917 100644 --- a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/ChargeRefunded.php +++ b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/ChargeRefunded.php @@ -20,6 +20,11 @@ class ChargeRefunded extends StripeEventListener */ public function processEvent(Event $event) { + /** + * @since 2.26.0 + */ + do_action('give_stripe_processing_charge_refunded', $event); + /* @var Charge $stripeCharge */ $stripeCharge = $event->data->object; $donation = $this->getDonation($event); diff --git a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/CheckoutSessionCompleted.php b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/CheckoutSessionCompleted.php index 42555fd86c..7f83edf4f7 100644 --- a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/CheckoutSessionCompleted.php +++ b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/CheckoutSessionCompleted.php @@ -2,8 +2,6 @@ namespace Give\PaymentGateways\Gateways\Stripe\Webhooks\Listeners; -use Give\Donations\Models\DonationNote; -use Give\Donations\Repositories\DonationRepository; use Give\Donations\ValueObjects\DonationStatus; use Give\PaymentGateways\Gateways\Stripe\Webhooks\StripeEventListener; use Stripe\Checkout\Session; @@ -22,6 +20,11 @@ class CheckoutSessionCompleted extends StripeEventListener */ public function processEvent(Event $event) { + /** + * @since 2.26.0 + */ + do_action('give_stripe_processing_checkout_session_completed', $event); + /* @var Session $checkoutSession */ $checkoutSession = $event->data->object; $donation = $this->getDonation($event); diff --git a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentPaymentFailed.php b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentPaymentFailed.php index 6788386319..e021a70bbb 100644 --- a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentPaymentFailed.php +++ b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentPaymentFailed.php @@ -16,6 +16,11 @@ class PaymentIntentPaymentFailed extends StripeEventListener */ public function processEvent(Event $event) { + /** + * @since 2.26.0 + */ + do_action('give_stripe_processing_payment_intent_failed', $event); + $donation = $this->getDonation($event); if (!$donation->status->isFailed()) { diff --git a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentSucceeded.php b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentSucceeded.php index 3efc333261..2708109c83 100644 --- a/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentSucceeded.php +++ b/src/PaymentGateways/Gateways/Stripe/Webhooks/Listeners/PaymentIntentSucceeded.php @@ -19,6 +19,11 @@ class PaymentIntentSucceeded extends StripeEventListener */ public function processEvent(Event $event) { + /** + * @since 2.26.0 + */ + do_action('give_stripe_processing_payment_intent_succeeded', $event); + /* @var PaymentIntent $paymentIntent */ $paymentIntent = $event->data->object; diff --git a/src/PaymentGateways/PayPalCommerce/Actions/GetPayPalOrderFromRequest.php b/src/PaymentGateways/PayPalCommerce/Actions/GetPayPalOrderFromRequest.php deleted file mode 100644 index 8adca5b38b..0000000000 --- a/src/PaymentGateways/PayPalCommerce/Actions/GetPayPalOrderFromRequest.php +++ /dev/null @@ -1,34 +0,0 @@ -getOrder($paypalOrderId); - } -} diff --git a/src/PaymentGateways/PayPalCommerce/Exceptions/PayPalOrderException.php b/src/PaymentGateways/PayPalCommerce/Exceptions/PayPalOrderException.php new file mode 100644 index 0000000000..911114fd49 --- /dev/null +++ b/src/PaymentGateways/PayPalCommerce/Exceptions/PayPalOrderException.php @@ -0,0 +1,9 @@ + 0, + ]; + + /** + * Create users for API authentication + * + * @since 2.26.0 + * + * @return void + */ + public static function wpSetUpBeforeClass(WP_UnitTest_Factory $factory) + { + $uses = array_flip(class_uses(static::class)); + + if (isset($uses[HasDefaultWordPressUsers::class])) { + self::$users = array_merge( + self::$users, + static::createDefaultWordPressUsers($factory) + ); + } + + if (isset($uses[HasDefaultGiveWPUsers::class])) { + self::$users = array_merge( + self::$users, + static::createDefaultGiveWPUsers($factory) + ); + } + } + + /** + * Initialize the REST server + * + * @since 2.26.0 + * + * @return void + */ + public function setUp() + { + parent::setUp(); + + global $wp_rest_server; + $this->server = $wp_rest_server = new WP_REST_Server; + do_action('rest_api_init'); + + $this->flushRoles(); + } + + /** + * Flushes the WordPress user roles and reloads them from the database. + * + * This function ensures that we are testing against the database data, not just in-memory data. + * + * @since 2.26.0 + * + * @return void + */ + private function flushRoles() + { + unset($GLOBALS['wp_user_roles']); + global $wp_roles; + $wp_roles = new WP_Roles(); + } + + /** + * Destroy the REST server + * + * @since 2.26.0 + * + * @return void + */ + public function tearDown() + { + parent::tearDown(); + + global $wp_rest_server; + $wp_rest_server = null; + } + + /** + * Delete users after the test class is done + * + * @since 2.26.0 + * + * @return void + */ + public static function wpTearDownAfterClass() + { + foreach (self::$users as $userRole => $userId) { + if (wp_delete_user($userId)) { + unset(self::$users[$userRole]); + } + } + } + + /** + * Wrapper for creating a request + * + * @since 2.26.0 + * + * @param string $method The HTTP method of the request (e.g. GET, POST, etc.). + * @param string $route The REST API route to access (e.g. /wp/v2/posts). + * @param array $attributes Optional. An array of attributes to set on the request object. + * @param string $userRole Optional. The role of the user to authenticate the request (e.g. anonymous, administrator, etc.). + * + * @return WP_REST_Request A new WP_REST_Request object with the specified parameters. + */ + public function createRequest( + string $method, + string $route, + array $attributes = [], + string $userRole = 'anonymous' + ): WP_REST_Request { + if ( ! in_array($userRole, array_keys(self::$users), true)) { + $userRole = 'anonymous'; + } + wp_set_current_user(self::$users[$userRole]); + + return new WP_REST_Request($method, $route, $attributes); + } + + /** + * Wrapper for dispatching a request + * + * @since 2.26.0 + * + * @param WP_REST_Request $request The request object to dispatch to the server. + * + * @return WP_REST_Response The response object returned by the server. + */ + public function dispatchRequest(WP_REST_Request $request): WP_REST_Response + { + return $this->server->dispatch($request); + } + + /** + * Asserts that the response is a WP error response with the specified code and status (if provided). + * + * @since 2.26.0 + * + * @param int|string $code The expected error code of the WP_Error object. + * @param mixed $response The response object to check (can be a WP_REST_Response or WP_Error object). + * @param int|null $status Optional. The expected error status of the WP_Error object (if any). + * + * @return void + */ + protected function assertErrorResponse($code, $response, $status = null) + { + if (is_a($response, 'WP_REST_Response')) { + $response = $response->as_error(); + } + + $this->assertInstanceOf('WP_Error', $response); + $this->assertEquals($code, $response->get_error_code()); + + if (null !== $status) { + $data = $response->get_error_data(); + $this->assertArrayHasKey('status', $data); + $this->assertEquals($status, $data['status']); + } + } +} diff --git a/tests/TestTraits/HasDefaultGiveWPUsers.php b/tests/TestTraits/HasDefaultGiveWPUsers.php new file mode 100644 index 0000000000..beea7cefe7 --- /dev/null +++ b/tests/TestTraits/HasDefaultGiveWPUsers.php @@ -0,0 +1,35 @@ +user->create(['role' => $role]); + } + + return $users; + } +} diff --git a/tests/TestTraits/HasDefaultWordPressUsers.php b/tests/TestTraits/HasDefaultWordPressUsers.php new file mode 100644 index 0000000000..116c69861b --- /dev/null +++ b/tests/TestTraits/HasDefaultWordPressUsers.php @@ -0,0 +1,35 @@ +user->create(['role' => $role]); + } + + return $users; + } +} diff --git a/tests/includes/legacy/importer/tests-donations.php b/tests/includes/legacy/importer/tests-donations.php index 30854b3e67..2758c7e696 100644 --- a/tests/includes/legacy/importer/tests-donations.php +++ b/tests/includes/legacy/importer/tests-donations.php @@ -349,36 +349,37 @@ public function test_to_check_donor_is_created() { } /** - * To test to check is donation form is created or not - * - * @since 2.1 - */ + * To test to check is donation form is created or not + * + * @since 2.1 + * @since 2.26.0 Replace deprecated get_page_by_title() with give_get_page_by_title(). + */ public function test_to_check_donation_form_is_created() { give_import_donation_report_reset(); $this->import_donation_in_live(); - $form = get_page_by_title( 'Make a wish Foundation', OBJECT, 'give_forms' ); - $this->assertTrue( ! empty( $form->ID ) ); + $form = give_get_page_by_title('Make a wish Foundation', OBJECT, 'give_forms'); + $this->assertTrue(! empty($form->ID)); $form = new Give_Donate_Form( $form->ID ); $id = $form->get_ID(); $this->assertTrue( ! empty( $id ) ); - $form = get_page_by_title( 'Save the Trees', OBJECT, 'give_forms' ); - $this->assertTrue( ! empty( $form->ID ) ); + $form = give_get_page_by_title('Save the Trees', OBJECT, 'give_forms'); + $this->assertTrue(! empty($form->ID)); $form = new Give_Donate_Form( $form->ID ); $id = $form->get_ID(); $this->assertTrue( ! empty( $id ) ); - $form = get_page_by_title( 'Help a Child', OBJECT, 'give_forms' ); - $this->assertTrue( ! empty( $form->ID ) ); + $form = give_get_page_by_title('Help a Child', OBJECT, 'give_forms'); + $this->assertTrue(! empty($form->ID)); $form = new Give_Donate_Form( $form->ID ); $id = $form->get_ID(); $this->assertTrue( ! empty( $id ) ); - $form = get_page_by_title( 'No Donation Form', OBJECT, 'give_forms' ); - $this->assertTrue( empty( $form->ID ) ); + $form = give_get_page_by_title('No Donation Form', OBJECT, 'give_forms'); + $this->assertTrue(empty($form->ID)); } diff --git a/webpack.mix.js b/webpack.mix.js index 001fb52d69..e28a643152 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -1,60 +1,70 @@ +const fs = require('fs'); const mix = require('laravel-mix'); const path = require('path'); const WebpackRTLPlugin = require('webpack-rtl-plugin'); const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extraction-webpack-plugin'); mix.setPublicPath('assets/dist') - .sass('assets/src/css/frontend/give-frontend.scss', 'css/give.css') - .sass('assets/src/css/admin/block-editor.scss', 'css/admin-block-editor.css') - .sass('assets/src/css/admin/give-admin.scss', 'css/admin.css') - .sass('assets/src/css/admin/give-admin-global.scss', 'css/admin-global.css') - .sass('assets/src/css/admin/setup.scss', 'css/admin-setup.css') - .sass('assets/src/css/admin/shortcodes.scss', 'css/admin-shortcode-button.css') - .sass('assets/src/css/admin/plugin-deactivation-survey.scss', 'css/') - .sass('assets/src/css/admin/widgets.scss', 'css/admin-widgets.css') - .sass('assets/src/css/admin/paypal-commerce.scss', 'css/admin-paypal-commerce.css') - .sass('src/Views/Form/Templates/Sequoia/assets/css/form.scss', 'css/give-sequoia-template.css') - .sass('src/Views/Form/Templates/Classic/resources/css/form.scss', 'css/give-classic-template.css') - .sass('src/MultiFormGoals/resources/css/common.scss', 'css/multi-form-goal-block.css') - .sass('src/DonationSummary/resources/css/summary.scss', 'css/give-donation-summary.css') + .sass('assets/src/css/frontend/give-frontend.scss', 'css/give.css') + .sass('assets/src/css/admin/block-editor.scss', 'css/admin-block-editor.css') + .sass('assets/src/css/admin/give-admin.scss', 'css/admin.css') + .sass('assets/src/css/admin/give-admin-global.scss', 'css/admin-global.css') + .sass('assets/src/css/admin/setup.scss', 'css/admin-setup.css') + .sass('assets/src/css/admin/shortcodes.scss', 'css/admin-shortcode-button.css') + .sass('assets/src/css/admin/plugin-deactivation-survey.scss', 'css/') + .sass('assets/src/css/admin/widgets.scss', 'css/admin-widgets.css') + .sass('assets/src/css/admin/paypal-commerce.scss', 'css/admin-paypal-commerce.css') + .sass('src/Views/Form/Templates/Sequoia/assets/css/form.scss', 'css/give-sequoia-template.css') + .sass('src/Views/Form/Templates/Classic/resources/css/form.scss', 'css/give-classic-template.css') + .sass('src/MultiFormGoals/resources/css/common.scss', 'css/multi-form-goal-block.css') + .sass('src/DonationSummary/resources/css/summary.scss', 'css/give-donation-summary.css') - .js('assets/src/js/frontend/give.js', 'js/') - .js('assets/src/js/frontend/give-stripe.js', 'js/') - .js('assets/src/js/frontend/give-stripe-sepa.js', 'js/') - .js('assets/src/js/frontend/give-stripe-becs.js', 'js/') - .js('assets/src/js/frontend/paypal-commerce/index.js', 'js/paypal-commerce.js') - .js('assets/src/js/admin/admin.js', 'js/') - .js('assets/src/js/admin/admin-setup.js', 'js/') - .js('assets/src/js/admin/plugin-deactivation-survey.js', 'js/') - .js('assets/src/js/admin/admin-add-ons.js', 'js/') - .js('assets/src/js/admin/admin-widgets.js', 'js/') - .js('assets/src/js/admin/reports/app.js', 'js/admin-reports.js') - .js('assets/src/js/admin/reports/widget.js', 'js/admin-reports-widget.js') - .js('assets/src/js/admin/onboarding-wizard/index.js', 'js/admin-onboarding-wizard.js') - .js('includes/admin/shortcodes/admin-shortcodes.js', 'js/') - .js('blocks/load.js', 'js/gutenberg.js') - .js('src/Views/Form/Templates/Sequoia/assets/js/form.js', 'js/give-sequoia-template.js') - .js('src/Views/Form/Templates/Classic/resources/js/form.js', 'js/give-classic-template.js') - .js('src/DonorDashboards/resources/js/app/index.js', 'js/donor-dashboards-app.js') - .js('src/DonorDashboards/resources/js/block/index.js', 'js/donor-dashboards-block.js') - .js('src/Log/Admin/index.js', 'js/give-log-list-table-app.js') - .js('src/MigrationLog/Admin/index.js', 'js/give-migrations-list-table-app.js') - .js('src/DonationSummary/resources/js/summary.js', 'js/give-donation-summary.js') - .js('src/Promotions/InPluginUpsells/resources/js/addons-admin-page.js', 'js/admin-upsell-addons-page.js') - .js( - 'src/Promotions/InPluginUpsells/resources/js/recurring-donations-settings-tab.js', - 'js/admin-upsell-recurring-donations-settings-tab.js' - ) - .ts('src/DonationForms/resources/admin-donation-forms.tsx', 'js/give-admin-donation-forms.js') - .ts('src/Donors/resources/admin-donors.tsx', 'js/give-admin-donors.js') - .ts('src/Donations/resources/index.tsx', 'js/give-admin-donations.js') - .ts('src/Subscriptions/resources/admin-subscriptions.tsx', 'js/give-admin-subscriptions.js') - .js('src/Promotions/InPluginUpsells/resources/js/sale-banner.js', 'js/admin-upsell-sale-banner.js') - .react() - .sourceMaps(false, 'source-map') + .js('assets/src/js/frontend/give.js', 'js/') + .js('assets/src/js/frontend/give-stripe.js', 'js/') + .js('assets/src/js/frontend/give-stripe-sepa.js', 'js/') + .js('assets/src/js/frontend/give-stripe-becs.js', 'js/') + .js('assets/src/js/frontend/paypal-commerce/index.js', 'js/paypal-commerce.js') + .js('assets/src/js/admin/admin.js', 'js/') + .js('assets/src/js/admin/admin-setup.js', 'js/') + .js('assets/src/js/admin/plugin-deactivation-survey.js', 'js/') + .js('assets/src/js/admin/admin-add-ons.js', 'js/') + .js('assets/src/js/admin/admin-widgets.js', 'js/') + .js('assets/src/js/admin/reports/app.js', 'js/admin-reports.js') + .js('assets/src/js/admin/reports/widget.js', 'js/admin-reports-widget.js') + .js('assets/src/js/admin/onboarding-wizard/index.js', 'js/admin-onboarding-wizard.js') + .js('includes/admin/shortcodes/admin-shortcodes.js', 'js/') + .js('blocks/load.js', 'js/gutenberg.js') + .js('src/Views/Form/Templates/Sequoia/assets/js/form.js', 'js/give-sequoia-template.js') + .js('src/Views/Form/Templates/Classic/resources/js/form.js', 'js/give-classic-template.js') + .js('src/DonorDashboards/resources/js/app/index.js', 'js/donor-dashboards-app.js') + .js('src/DonorDashboards/resources/js/block/index.js', 'js/donor-dashboards-block.js') + .js('src/Log/Admin/index.js', 'js/give-log-list-table-app.js') + .js('src/MigrationLog/Admin/index.js', 'js/give-migrations-list-table-app.js') + .js('src/DonationSummary/resources/js/summary.js', 'js/give-donation-summary.js') + .js('src/Promotions/InPluginUpsells/resources/js/addons-admin-page.js', 'js/admin-upsell-addons-page.js') + .js( + 'src/Promotions/InPluginUpsells/resources/js/recurring-donations-settings-tab.js', + 'js/admin-upsell-recurring-donations-settings-tab.js', + ) + .ts('src/DonationForms/resources/admin-donation-forms.tsx', 'js/give-admin-donation-forms.js') + .ts('src/Donors/resources/admin-donors.tsx', 'js/give-admin-donors.js') + .ts('src/Donations/resources/index.tsx', 'js/give-admin-donations.js') + .ts('src/Subscriptions/resources/admin-subscriptions.tsx', 'js/give-admin-subscriptions.js') + .js('src/Promotions/InPluginUpsells/resources/js/sale-banner.js', 'js/admin-upsell-sale-banner.js') + .react() + .sourceMaps(false, 'source-map') - .copyDirectory('assets/src/images', 'assets/dist/images') - .copyDirectory('assets/src/fonts', 'assets/dist/fonts'); + .css('node_modules/@givewp/design-system-foundation/css/foundation.css', 'css/design-system/foundation.css') + .after(() => { + // Store the design system version in a file + const packageJson = require('./node_modules/@givewp/design-system-foundation/package.json'); + const version = packageJson.version; + + fs.writeFileSync('./assets/dist/css/design-system/version', version); + }) + + .copyDirectory('assets/src/images', 'assets/dist/images') + .copyDirectory('assets/src/fonts', 'assets/dist/fonts'); mix.webpackConfig({ resolve: {