diff --git a/.travis.yml b/.travis.yml index 8755499..6f74bab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,27 @@ php: env: - TRAVISCI_RUN=codesniff - - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 - - TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 - - TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=master + - TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 dist: trusty -sudo: false +# See https://github.com/WordPoints/wordpoints/issues/757 +group: deprecated-2017Q4 + +sudo: true addons: apt: @@ -31,28 +45,94 @@ matrix: # Use Ubuntu Precise because Trusty doesn't support PHP 5.2 or 5.3. - php: 5.3 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=release/3.3 - php: 5.3 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=master - php: 5.3 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.3 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 - php: 5.2 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=master - php: 5.2 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master WC_VERSION=release/3.3 - php: 5.2 dist: precise - env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=develop WC_VERSION=release/3.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.9 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 5.2 + dist: precise + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 # Only run nightly against trunk. - php: nightly env: TRAVISCI_RUN=phpunit WP_VERSION=develop WC_VERSION=master exclude: # The codesniff pass only needs to be run once. - - php: 7.2 + - php: 7.1 env: TRAVISCI_RUN=codesniff - php: 7.0 env: TRAVISCI_RUN=codesniff @@ -62,9 +142,21 @@ matrix: env: TRAVISCI_RUN=codesniff - php: 5.4 env: TRAVISCI_RUN=codesniff + # WordPress below 4.9 is not compatible with PHP 7.2. + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=master + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=develop WC_VERSION=release/3.3 + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.8 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=master + - php: 7.2 + env: TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master WC_VERSION=release/3.3 allow_failures: - php: nightly - - php: 7.2 fast_finish: true before_script: diff --git a/.wp-l10n-validator-cache.json b/.wp-l10n-validator-cache.json index 61fc180..34f9397 100644 --- a/.wp-l10n-validator-cache.json +++ b/.wp-l10n-validator-cache.json @@ -66,8 +66,8 @@ "errors": false }, "\/classes\/entity\/order.php": { - "size": 1261, - "hash": "60f9a0940017a491c69437ec8502baa4", + "size": 1199, + "hash": "415cfc52bb9185a21bd9bb78a2ddbd20", "errors": false }, "\/classes\/entity\/product\/review.php": { @@ -101,23 +101,33 @@ "errors": false }, "\/classes\/index.php": { - "size": 1913, - "hash": "480685c940669dbee6a7f5b7509c7a7a", + "size": 2053, + "hash": "8428f150e024cf62be9c31048d3197fc", + "errors": false + }, + "\/classes\/installable.php": { + "size": 871, + "hash": "46becfb0e95e9879b22c56de3f28b323", + "errors": false + }, + "\/classes\/updater\/1\/2\/0\/gateway.php": { + "size": 731, + "hash": "ad835a26346d440ed8d239aaa32ba016", "errors": false }, "\/components\/points\/includes\/actions.php": { - "size": 267, - "hash": "a3017b6ce609d09e076a2879b7d3cc83", + "size": 270, + "hash": "7a91e286beac7b353a0162293c702172", "errors": false }, "\/components\/points\/includes\/functions.php": { - "size": 1946, - "hash": "53ee7cc85e34e751788a7951331ffff9", + "size": 10836, + "hash": "84f62bd625257e0daa25f8978ac27df2", "errors": false }, "\/components\/points\/includes\/gateways\/points.php": { - "size": 11019, - "hash": "118800240714c3271cfbd2ffec2734d6", + "size": 11041, + "hash": "14c412352bf8a6484afab0da0476d79e", "errors": false }, "\/components\/points\/includes\/index.php": { @@ -126,35 +136,35 @@ "errors": false }, "\/components\/points\/points.php": { - "size": 547, - "hash": "7f3c9dfa9bee16c2b1b715be84877045", + "size": 541, + "hash": "a4705c37e76367027f431539344c8e37", "errors": false }, "\/includes\/actions.php": { - "size": 687, - "hash": "1a6757c91ed59f6b3f91a9849ffdaeab", + "size": 690, + "hash": "9a3fd655d5050a230bd2522c09eeb0d7", "errors": false }, "\/includes\/class-un-installer.php": { - "size": 1409, - "hash": "11caa402d800f536bfee3910daec3c3e", + "size": 1145, + "hash": "0c01188f924c4bcb3b00f1e93afba4b1", "errors": false }, "\/includes\/constants.php": { - "size": 457, - "hash": "160828d1993b8bd70f89fca29d65b85c", + "size": 458, + "hash": "96c6c795b3f7871d3ca1d9a2d5c3a1e6", "errors": false }, "\/includes\/functions.php": { - "size": 5795, - "hash": "002b34c80c573d7dd23f421f087e434a", + "size": 5804, + "hash": "1fa8266af9679df2f99242b207d29249", "errors": false }, "\/woocommerce.php": { - "size": 1522, - "hash": "8fa47d1b3a9f8a56739e3d073cd4763f", + "size": 1543, + "hash": "c8711aa061765366f720edf049dd0446", "errors": false } }, - "config_signature": "1ca1af7095becd556417f9dd5dd6403f" + "config_signature": "50c54bae94b7a74ab11249b59e47fdbb" } \ No newline at end of file diff --git a/.wp-l10n-validator-ignores-cache.json b/.wp-l10n-validator-ignores-cache.json index cdd4fd9..f698dd7 100644 --- a/.wp-l10n-validator-ignores-cache.json +++ b/.wp-l10n-validator-ignores-cache.json @@ -1,7 +1,7 @@ { "\/components\/points\/includes\/gateways\/points.php": { "products": { - "30": { + "27": { "name": "array", "type": "unknown", "args_started": true, @@ -10,7 +10,7 @@ } }, "refunds": { - "30": { + "27": { "name": "array", "type": "unknown", "args_started": true, @@ -40,13 +40,6 @@ } }, "text": { - "137": { - "name": "array", - "type": "unknown", - "args_started": true, - "arg_count": 1, - "parentheses": 1 - }, "144": { "name": "array", "type": "unknown", @@ -80,21 +73,21 @@ } }, "fail": { - "276": { + "279": { "name": "array", "type": "unknown", "args_started": true, "arg_count": 0, "parentheses": 1 }, - "305": { + "308": { "name": "array", "type": "unknown", "args_started": true, "arg_count": 0, "parentheses": 1 }, - "330": { + "333": { "name": "array", "type": "unknown", "args_started": true, @@ -103,7 +96,7 @@ } }, "success": { - "340": { + "343": { "name": "array", "type": "unknown", "args_started": true, @@ -176,5 +169,23 @@ "parentheses": 1 } } + }, + "\/components\/points\/includes\/functions.php": { + "number": { + "133": { + "name": "array", + "type": "unknown", + "args_started": true, + "arg_count": 3, + "parentheses": 1 + } + }, + "refund": { + "301": false, + "330": false + }, + "charge": { + "303": false + } } } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6829fb0..59c93cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CH Nothing documented right now. +## [1.3.0] - 2018-02-10 + +### Added + +- Coupon settings that allow a certain number of points to be charged each time a coupon is used. + +### Fixed + +- Added missing `id` attribute to points type dropdown for the points gateway at checkout. + ## [1.2.1] - 2017-10-16 ### Requires @@ -99,6 +109,7 @@ Nothing documented right now. - How many points to charge for the monetary amount can be set to 1-to-1 or 1-to-100. [unreleased]: https://github.com/WordPoints/woocommerce/compare/master...HEAD +[1.3.0]: https://github.com/WordPoints/woocommerce/compare/1.2.1...1.3.0 [1.2.1]: https://github.com/WordPoints/woocommerce/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/WordPoints/woocommerce/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/WordPoints/woocommerce/compare/1.0.2...1.1.0 diff --git a/composer.lock b/composer.lock index 556f5aa..21ad025 100644 --- a/composer.lock +++ b/composer.lock @@ -9,16 +9,16 @@ "packages-dev": [ { "name": "jdgrimes/wpppb", - "version": "0.3.0", + "version": "0.3.5", "source": { "type": "git", "url": "https://github.com/JDGrimes/wpppb.git", - "reference": "94c7ef9bce976ffc08b1174cc5e61f3b6e7435c4" + "reference": "faaac99b5a187b700cdb3eb16f0989f4eee975ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JDGrimes/wpppb/zipball/94c7ef9bce976ffc08b1174cc5e61f3b6e7435c4", - "reference": "94c7ef9bce976ffc08b1174cc5e61f3b6e7435c4", + "url": "https://api.github.com/repos/JDGrimes/wpppb/zipball/faaac99b5a187b700cdb3eb16f0989f4eee975ce", + "reference": "faaac99b5a187b700cdb3eb16f0989f4eee975ce", "shasum": "" }, "require": { @@ -47,7 +47,7 @@ ], "description": "Bootstrap for integration testing WordPress plugins with PHPUnit", "homepage": "https://github.com/JDGrimes/wpppb", - "time": "2017-10-06T16:14:08+00:00" + "time": "2018-01-03T22:31:21+00:00" }, { "name": "xrstf/composer-php52", diff --git a/dev-lib b/dev-lib index dbc68f2..e9faf8e 160000 --- a/dev-lib +++ b/dev-lib @@ -1 +1 @@ -Subproject commit dbc68f2973275aad57ee500a681a7acbc821c00b +Subproject commit e9faf8e3211cad0f72df2b0bcdc4b246c1734996 diff --git a/src/classes/installable.php b/src/classes/installable.php index 52c6965..115cad0 100644 --- a/src/classes/installable.php +++ b/src/classes/installable.php @@ -43,6 +43,14 @@ protected function get_uninstall_routine_factories() { array( 'woocommerce_wordpoints_points_settings' ) ); + $factories[] = new WordPoints_Uninstaller_Factory_Metadata( + 'post' + , array( + 'wordpoints_woocommerce_points_coupon_cost', + 'wordpoints_woocommerce_points_coupon_points_type', + ) + ); + return $factories; } } diff --git a/src/components/points/includes/functions.php b/src/components/points/includes/functions.php index d63d46c..89a251f 100644 --- a/src/components/points/includes/functions.php +++ b/src/components/points/includes/functions.php @@ -12,7 +12,7 @@ * * @since 1.0.0 */ -function wordpoints_points_logs_woocommerce_points_gateway( +function wordpoints_points_logs_woocommerce_points_gateway( // WPCS: prefix OK. $text, $points, $points_type, $user_id, $log_type, $meta ) { @@ -51,7 +51,7 @@ function wordpoints_points_logs_woocommerce_points_gateway( * * @since 1.0.0 */ -function wordpoints_points_logs_woocommerce_points_gateway_refund( +function wordpoints_points_logs_woocommerce_points_gateway_refund( // WPCS: prefix OK. $text, $points, $points_type, $user_id, $log_type, $meta ) { @@ -86,4 +86,344 @@ function wordpoints_points_logs_woocommerce_points_gateway_refund( , 6 ); +/** + * Filters the tabs for the coupon settings. + * + * Adds the points tab. + * + * @since 1.3.0 + * + * @param array $tabs The coupon settings tabs. + * + * @return array The filtered coupon settings tabs. + */ +function wordpoints_woocommerce_points_coupon_tabs_filter( $tabs ) { + + $tabs['wordpoints_points'] = array( + 'label' => __( 'Points', 'wordpoints-woocommerce' ), + 'target' => 'wordpoints_points_coupon_data', + 'class' => '', + ); + + return $tabs; +} +add_filter( 'woocommerce_coupon_data_tabs', 'wordpoints_woocommerce_points_coupon_tabs_filter' ); + +/** + * Displays the points coupon data panel. + * + * @since 1.3.0 + * + * @param int $coupon_id The ID of the coupon the settings are being shown for. + * @param WC_Coupon $coupon The coupon the settings are being shown for. + */ +function wordpoints_woocommerce_points_coupon_data_panel( $coupon_id, $coupon ) { + + ?> + +
+ 'wordpoints_woocommerce_points_coupon_cost', + 'label' => __( 'Points cost per use', 'wordpoints-woocommerce' ), + 'description' => __( 'Number of points it costs to use this coupon. Leave blank to not charge points for this coupon.', 'wordpoints-woocommerce' ), + 'type' => 'number', + 'desc_tip' => true, + ) + ); + + // Points type. + woocommerce_wp_select( + array( + 'id' => 'wordpoints_woocommerce_points_coupon_points_type', + 'label' => __( 'Points type', 'wordpoints-woocommerce' ), + 'options' => wp_list_pluck( wordpoints_get_points_types(), 'name' ), + ) + ); + + /** + * Fires at the bottom of the points coupon settings panel. + * + * @since 1.3.0 + * + * @param int $coupon_id The ID of the coupon the settings are being shown for. + * @param WC_Coupon $coupon The coupon the settings are being shown for. + */ + do_action( 'wordpoints_woocommerce_points_coupon_options', $coupon_id, $coupon ); + + ?> +
+ + update_meta_data( + 'wordpoints_woocommerce_points_coupon_points_type', + sanitize_key( $_POST['wordpoints_woocommerce_points_coupon_points_type'] ) // WPCS: CSRF OK. + ); + + $coupon->update_meta_data( + 'wordpoints_woocommerce_points_coupon_cost' + , wordpoints_posint( $_POST['wordpoints_woocommerce_points_coupon_cost'] ) // WPCS: CSRF OK. + ); + + $coupon->save(); + } +} +add_action( 'woocommerce_coupon_options_save', 'wordpoints_woocommerce_points_coupon_options_save', 10, 2 ); + +/** + * Validates a coupon based on the user's points. + * + * @since 1.3.0 + * + * @throws Exception If the user has insufficient points to use the coupon. + * + * @param bool $is_valid Whether the coupon is valid. + * @param WC_Coupon $coupon The coupon being validated. + * + * @return bool Whether the coupon is valid. + */ +function wordpoints_woocommerce_points_coupon_is_valid( $is_valid, $coupon ) { + + if ( $is_valid ) { + + $points = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_cost' ); + + if ( wordpoints_posint( $points ) ) { + + $points_type = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_points_type' ); + + $user_id = get_current_user_id(); + + $user_points = wordpoints_get_points( $user_id, $points_type ); + + if ( $user_points < $points ) { + throw new Exception( + sprintf( + // translators: 1: Coupon price in points. 2: Number of points the user has. + __( 'This coupon costs %1$s, you have only %2$s.', 'wordpoints-woocommerce' ) + , wordpoints_format_points( + $points + , $points_type + , 'woocommerce_coupon_error_cost' + ) + , wordpoints_format_points( + $user_points + , $points_type + , 'woocommerce_coupon_error_balance' + ) + ) + , 100172001 // Creative prefixing: 1 W0RDp01N72 001 + ); + } + } + } + + return $is_valid; +} +add_filter( 'woocommerce_coupon_is_valid', 'wordpoints_woocommerce_points_coupon_is_valid', 10, 2 ); + +/** + * Filters the coupon amount shown on the checkout page. + * + * Appends the points cost of the coupon, if any. + * + * @since 1.3.0 + * + * @param string $html The HTML of the coupon discount amount. + * @param WC_Coupon $coupon The coupon. + * + * @return string The coupon amount HTML. + */ +function wordpoints_woocommerce_points_coupon_amount_html_filter( $html, $coupon ) { + + $points = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_cost' ); + + if ( wordpoints_posint( $points ) ) { + + $points_type = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_points_type' ); + + $cost = sprintf( + // translators: Coupon price in points. + __( '(Cost: %s)', 'wordpoints-woocommerce' ) + , wordpoints_format_points( + $points + , $points_type + , 'woocommerce_coupon_cost' + ) + ); + + $html .= ' ' . $cost . ''; + } + + return $html; +} +add_filter( 'woocommerce_coupon_discount_amount_html', 'wordpoints_woocommerce_points_coupon_amount_html_filter', 10, 2 ); + +/** + * Update used coupon amount for each coupon within an order. + * + * @since 1.3.0 + * + * @param int $order_id The oder ID. + */ +function wordpoints_woocommerce_points_coupons_apply( $order_id ) { + + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + return; + } + + $has_recorded = $order->get_data_store()->get_recorded_coupon_usage_counts( $order ); + + if ( $order->has_status( 'cancelled' ) && $has_recorded ) { + $action = 'refund'; + } elseif ( ! $order->has_status( 'cancelled' ) && ! $has_recorded ) { + $action = 'charge'; + } else { + return; + } + + $user_id = $order->get_user_id(); + + if ( ! $user_id ) { + return; + } + + foreach ( $order->get_used_coupons() as $code ) { + + if ( ! $code ) { + continue; + } + + $coupon = new WC_Coupon( $code ); + + $points = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_cost' ); + + if ( ! wordpoints_posint( $points ) ) { + continue; + } + + $points_type = $coupon->get_meta( 'wordpoints_woocommerce_points_coupon_points_type' ); + + $points = 'refund' === $action ? $points : -$points; + + wordpoints_alter_points( + $user_id + , $points + , $points_type + , "woocommerce_coupon_{$action}" + , array( + 'order_id' => $order_id, + 'coupon_id' => $coupon->get_id(), + ) + ); + } +} +add_action( 'woocommerce_order_status_pending', 'wordpoints_woocommerce_points_coupons_apply', 5 ); +add_action( 'woocommerce_order_status_completed', 'wordpoints_woocommerce_points_coupons_apply', 5 ); +add_action( 'woocommerce_order_status_processing', 'wordpoints_woocommerce_points_coupons_apply', 5 ); +add_action( 'woocommerce_order_status_on-hold', 'wordpoints_woocommerce_points_coupons_apply', 5 ); +add_action( 'woocommerce_order_status_cancelled', 'wordpoints_woocommerce_points_coupons_apply', 5 ); + +/** + * Render logs for when a coupon was used on an order. + * + * @since 1.3.0 + */ +function wordpoints_woocommerce_points_logs_coupon_charge( + $text, $points, $points_type, $user_id, $log_type, $meta +) { + + if ( isset( $meta['order_id'] ) ) { + + $order = new WC_Order( $meta['order_id'] ); + + // Back-compat for pre-WC 3.0.0. + if ( ! method_exists( $order, 'get_id' ) ) { + $order_id = $order->id; + } else { + $order_id = $order->get_id(); + } + + if ( $order_id ) { + + return sprintf( + // translators: Order number. + _x( 'Coupon used on order %s', 'points log description', 'wordpoints-woocommerce' ) + , $order->get_order_number() + ); + } + } + + return _x( 'Coupon used on an order.', 'points log description', 'wordpoints-woocommerce' ); +} +add_filter( + 'wordpoints_points_log-woocommerce_coupon_charge' + , 'wordpoints_woocommerce_points_logs_coupon_charge' + , 10 + , 6 +); + +/** + * Render logs for when a coupon used on an order is cancelled. + * + * @since 1.3.0 + */ +function wordpoints_woocommerce_points_logs_coupon_refund( + $text, $points, $points_type, $user_id, $log_type, $meta +) { + + if ( isset( $meta['order_id'] ) ) { + + $order = new WC_Order( $meta['order_id'] ); + + // Back-compat for pre-WC 3.0.0. + if ( ! method_exists( $order, 'get_id' ) ) { + $order_id = $order->id; + } else { + $order_id = $order->get_id(); + } + + if ( $order_id ) { + + return sprintf( + // translators: Order number. + _x( 'Refunded coupon for order %s', 'points log description', 'wordpoints-woocommerce' ) + , $order->get_order_number() + ); + } + } + + return _x( 'Refunded coupon for an order.', 'points log description', 'wordpoints-woocommerce' ); + +} +add_filter( + 'wordpoints_points_log-woocommerce_coupon_refund' + , 'wordpoints_woocommerce_points_logs_coupon_refund' + , 10 + , 6 +); + // EOF diff --git a/src/components/points/includes/gateways/points.php b/src/components/points/includes/gateways/points.php index 78c3a19..ac36f52 100644 --- a/src/components/points/includes/gateways/points.php +++ b/src/components/points/includes/gateways/points.php @@ -203,7 +203,10 @@ public function points_type_field() { $dropdown = new WordPoints_Dropdown_Builder( wp_list_pluck( $points_types, 'name' ) - , array( 'name' => "{$this->id}-points-type" ) + , array( + 'id' => "{$this->id}-points-type", + 'name' => "{$this->id}-points-type", + ) ); $dropdown->display(); diff --git a/src/includes/constants.php b/src/includes/constants.php index 9a8c629..209864e 100644 --- a/src/includes/constants.php +++ b/src/includes/constants.php @@ -14,7 +14,7 @@ * * @type string WORDPOINTS_WOOCOMMERCE_VERSION */ -define( 'WORDPOINTS_WOOCOMMERCE_VERSION', '1.2.1' ); +define( 'WORDPOINTS_WOOCOMMERCE_VERSION', '1.3.0' ); /** * The full path to the extension's main directory. diff --git a/src/languages/wordpoints-woocommerce.pot b/src/languages/wordpoints-woocommerce.pot index 341c97f..3eef262 100644 --- a/src/languages/wordpoints-woocommerce.pot +++ b/src/languages/wordpoints-woocommerce.pot @@ -1,14 +1,14 @@ -# Copyright (C) 2017 J.D. Grimes +# Copyright (C) 2018 J.D. Grimes # This file is distributed under the same license as the WordPoints WooCommerce package. msgid "" msgstr "" -"Project-Id-Version: WordPoints WooCommerce 1.2.1\n" +"Project-Id-Version: WordPoints WooCommerce 1.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-11 21:40:32+00:00\n" +"POT-Creation-Date: 2018-02-10 15:19:29+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2018-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -136,6 +136,56 @@ msgctxt "points log description" msgid "Refunded payment for an order." msgstr "" +#: components/points/includes/functions.php:103 +msgid "Points" +msgstr "" + +#: components/points/includes/functions.php:131 +msgid "Points cost per use" +msgstr "" + +#: components/points/includes/functions.php:132 +msgid "" +"Number of points it costs to use this coupon. Leave blank to not charge " +"points for this coupon." +msgstr "" + +#: components/points/includes/functions.php:142 +msgid "Points type" +msgstr "" + +#. translators: 1: Coupon price in points. 2: Number of points the user has. +#: components/points/includes/functions.php:224 +msgid "This coupon costs %1$s, you have only %2$s." +msgstr "" + +#. translators: Coupon price in points. +#: components/points/includes/functions.php:268 +msgid "(Cost: %s)" +msgstr "" + +#. translators: Order number. +#: components/points/includes/functions.php:374 +msgctxt "points log description" +msgid "Coupon used on order %s" +msgstr "" + +#: components/points/includes/functions.php:380 +msgctxt "points log description" +msgid "Coupon used on an order." +msgstr "" + +#. translators: Order number. +#: components/points/includes/functions.php:413 +msgctxt "points log description" +msgid "Refunded coupon for order %s" +msgstr "" + +#: components/points/includes/functions.php:419 +msgctxt "points log description" +msgid "Refunded coupon for an order." +msgstr "" + #: components/points/includes/gateways/points.php:28 msgctxt "gateway title" msgid "WordPoints" @@ -209,33 +259,33 @@ msgstr "" msgid "Points Type" msgstr "" -#: components/points/includes/gateways/points.php:240 +#: components/points/includes/gateways/points.php:243 msgid "Payment Error:" msgstr "" -#: components/points/includes/gateways/points.php:241 +#: components/points/includes/gateways/points.php:244 msgid "Please select a points type to pay with." msgstr "" -#: components/points/includes/gateways/points.php:296 -#: components/points/includes/gateways/points.php:321 +#: components/points/includes/gateways/points.php:299 +#: components/points/includes/gateways/points.php:324 msgid "Payment error:" msgstr "" -#: components/points/includes/gateways/points.php:297 +#: components/points/includes/gateways/points.php:300 msgid "You have insufficient points to make this purchase." msgstr "" -#: components/points/includes/gateways/points.php:322 +#: components/points/includes/gateways/points.php:325 msgid "Unable to subtract the points from your account." msgstr "" #. translators: The number of points refunded. -#: components/points/includes/gateways/points.php:393 +#: components/points/includes/gateways/points.php:396 msgid "Refunded %s points." msgstr "" -#: components/points/includes/gateways/points.php:480 +#: components/points/includes/gateways/points.php:483 msgid "" "Sorry, you must be logged in and have sufficient points in order to check " "out." diff --git a/src/woocommerce.php b/src/woocommerce.php index 15d95ee..3167156 100644 --- a/src/woocommerce.php +++ b/src/woocommerce.php @@ -4,11 +4,11 @@ * WordPoints WooCommerce integration extension. * * ---------------------------------------------------------------------------------| - * Copyright 2014-17 J.D. Grimes (email : jdg@codesymphony.co) + * Copyright 2014-18 J.D. Grimes (email : jdg@codesymphony.co) * ---------------------------------------------------------------------------------| * * @package WordPoints_WooCommerce - * @version 1.2.1 + * @version 1.3.0 * @author J.D. Grimes */ @@ -18,7 +18,7 @@ Extension URI: https://wordpoints.org/extensions/woocommerce/ Author: J.D. Grimes Author URI: https://codesymphony.co/ - Version: 1.2.1 + Version: 1.3.0 Description: Let your users pay with points. Text Domain: wordpoints-woocommerce Domain Path: /languages diff --git a/tests/codeception/_support/AcceptanceTester.php b/tests/codeception/_support/AcceptanceTester.php index d69aee6..b82d0aa 100644 --- a/tests/codeception/_support/AcceptanceTester.php +++ b/tests/codeception/_support/AcceptanceTester.php @@ -13,7 +13,84 @@ * @since 1.2.0 */ class AcceptanceTester extends \WordPoints\Tests\Codeception\AcceptanceTester { - // Add any methods here that you want. + + /** + * Logs in as a customer. + * + * @since 1.3.0 + * + * @return int The customer user ID. + */ + public function amLoggedInAsCustomer() { + + $user_id = wp_insert_user( array( 'user_login' => 'customer', 'user_pass' => 'password' ) ); + update_user_meta( $user_id, 'billing_country', 'US' ); + update_user_meta( $user_id, 'billing_first_name', 'Joe' ); + update_user_meta( $user_id, 'billing_last_name', 'Tester' ); + update_user_meta( $user_id, 'billing_address_1', '12 Bukle Mishue' ); + update_user_meta( $user_id, 'billing_city', 'Hebron' ); + update_user_meta( $user_id, 'billing_state', 'MD' ); + update_user_meta( $user_id, 'billing_postcode', '12345' ); + update_user_meta( $user_id, 'billing_email', 'test@example.com' ); + update_user_meta( $user_id, 'billing_phone', '777-777-777-777' ); + + wp_set_current_user( $user_id ); + + $I = $this; + $I->loginAs( 'customer', 'password' ); + + return $user_id; + } + + /** + * Creates a product to use in the test. + * + * @since 1.3.0 + * + * @return int The product ID. + */ + public function hadCreatedAProduct() { + + $factory = new WordPoints_WooCommerce_UnitTest_Factory_For_Product(); + + return $factory->create(); + } + + /** + * Creates a points coupon to use in the tests. + * + * @since 1.3.0 + * + * @param string $points_type The points type to create the coupon for. + * @param int $amount The number of points the coupon should cost. + * + * @return WC_Coupon The coupon. + */ + public function hadCreatedAPointsCoupon( $points_type = 'points', $amount = 10 ) { + + $coupon = WC_Helper_Coupon::create_coupon( 'fake-coupon' ); + $coupon->set_amount( 5 ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_points_type', $points_type ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_cost', $amount ); + $coupon->save(); + + return $coupon; + } + + /** + * Sets the conversion rate for the points gateway. + * + * @since 1.3.0 + * + * @param string $points_type The points type to set the conversion rate for. + * @param int $rate The conversion rate to set. + */ + public function hadSetPointsConversionRate( $points_type = 'points', $rate = 100 ) { + + $gateway = new WordPoints_WooCommerce_Gateway_Points(); + $gateway->settings[ "conversion_rate-{$points_type}" ] = $rate; + update_option( $gateway->get_option_key(), $gateway->settings ); + } } // EOF diff --git a/tests/codeception/acceptance/bootstrap.php b/tests/codeception/acceptance/bootstrap.php index f8ff482..18565e4 100644 --- a/tests/codeception/acceptance/bootstrap.php +++ b/tests/codeception/acceptance/bootstrap.php @@ -21,4 +21,11 @@ */ require_once __DIR__ . '/../../phpunit/includes/factories/order.php'; +/** + * Load the WooCommerce tests bootstrap. + * + * @since 1.3.0 + */ +require_once WP_PLUGIN_DIR . '/woocommerce/tests/bootstrap.php'; + // EOF diff --git a/tests/codeception/acceptance/coupon/checkout.cept.php b/tests/codeception/acceptance/coupon/checkout.cept.php new file mode 100644 index 0000000..2c1f4b9 --- /dev/null +++ b/tests/codeception/acceptance/coupon/checkout.cept.php @@ -0,0 +1,46 @@ +cart->empty_cart(); + +$I = new AcceptanceTester( $scenario ); +$I->wantTo( 'Use a points coupon at checkout.' ); + +$product_id = $I->hadCreatedAProduct(); + +$I->hadCreatedAPointsType(); +$I->hadCreatedAPointsType( array( 'name' => 'Test' ) ); + +$I->hadCreatedAPointsCoupon( 'test' ); + +$I->hadSetPointsConversionRate(); +$I->hadSetPointsConversionRate( 'test', 50 ); + +$user_id = $I->amLoggedInAsCustomer(); + +wordpoints_set_points( $user_id, 100, 'test', 'test' ); +wordpoints_set_points( $user_id, 10000, 'points', 'test' ); + +$I->amOnPage( str_replace( home_url(), '', get_permalink( $product_id ) ) ); +$I->click( 'Add to cart' ); +$I->amOnPage( str_replace( home_url(), '', wc_get_page_permalink( 'cart' ) ) ); +$I->fillField( 'Coupon:', 'fake-coupon' ); +$I->click( 'Apply coupon' ); +$I->waitForJqueryAjax(); +$I->click( 'Proceed to checkout' ); +$I->waitForElementVisible( '[name=wordpoints_points-points-type]' ); +$I->selectOption( 'wordpoints_points-points-type', 'Points' ); +$I->click( 'Place order' ); +$I->waitForElementNotVisible( '.blockOverlay' ); +$I->see( 'Thank you. Your order has been received.' ); + +PHPUnit_Framework_Assert::assertSame( 90, wordpoints_get_points( $user_id, 'test' ) ); + +// EOF diff --git a/tests/codeception/acceptance/coupon/settings.cept.php b/tests/codeception/acceptance/coupon/settings.cept.php new file mode 100644 index 0000000..0a2faa7 --- /dev/null +++ b/tests/codeception/acceptance/coupon/settings.cept.php @@ -0,0 +1,23 @@ +wantTo( 'Save coupon points settings.' ); +$I->hadCreatedAPointsType(); +$I->amLoggedInAsAdminOnPage( + '/wp-admin/post-new.php?post_type=shop_coupon' +); +$I->fillField( 'coupon_amount', '10' ); +$I->click( 'Points', '.coupon_data_tabs' ); +$I->fillField( 'wordpoints_woocommerce_points_coupon_cost', '10' ); +$I->click( 'Publish' ); +$I->click( 'Points', '.coupon_data_tabs' ); +$I->seeInField( 'wordpoints_woocommerce_points_coupon_cost', '10' ); + +// EOF diff --git a/tests/codeception/acceptance/gateway/checkout.cept.php b/tests/codeception/acceptance/gateway/checkout.cept.php index 3c0e4ce..57664e4 100644 --- a/tests/codeception/acceptance/gateway/checkout.cept.php +++ b/tests/codeception/acceptance/gateway/checkout.cept.php @@ -7,43 +7,29 @@ * @since 1.2.0 */ -$factory = new WordPoints_WooCommerce_UnitTest_Factory_For_Product(); -$product_id = $factory->create(); - -$user_id = wp_insert_user( array( 'user_login' => 'customer', 'user_pass' => 'password' ) ); -update_user_meta( $user_id, 'billing_country', 'US' ); -update_user_meta( $user_id, 'billing_first_name', 'Joe' ); -update_user_meta( $user_id, 'billing_last_name', 'Tester' ); -update_user_meta( $user_id, 'billing_address_1', '12 Bukle Mishue' ); -update_user_meta( $user_id, 'billing_city', 'Hebron' ); -update_user_meta( $user_id, 'billing_state', 'MD' ); -update_user_meta( $user_id, 'billing_postcode', '12345' ); -update_user_meta( $user_id, 'billing_email', 'test@example.com' ); -update_user_meta( $user_id, 'billing_phone', '777-777-777-777' ); - -wp_set_current_user( $user_id ); - -$gateway = new WordPoints_WooCommerce_Gateway_Points(); -$gateway->settings['conversion_rate-points'] = 100; -$gateway->settings['conversion_rate-test'] = 50; -update_option( $gateway->get_option_key(), $gateway->settings ); - wc_empty_cart(); WC()->cart->empty_cart(); $I = new AcceptanceTester( $scenario ); $I->wantTo( 'Checkout with points gateway.' ); +$product_id = $I->hadCreatedAProduct(); + $I->hadCreatedAPointsType(); $I->hadCreatedAPointsType( array( 'name' => 'Test' ) ); +$I->hadCreatedAPointsCoupon( 'test' ); + +$I->hadSetPointsConversionRate(); +$I->hadSetPointsConversionRate( 'test', 50 ); + +$user_id = $I->amLoggedInAsCustomer(); + wordpoints_set_points( $user_id, 10000, 'test', 'test' ); -$I->loginAs( 'customer', 'password' ); $I->amOnPage( str_replace( home_url(), '', get_permalink( $product_id ) ) ); $I->click( 'Add to cart' ); $I->amOnPage( str_replace( home_url(), '', wc_get_page_permalink( 'checkout' ) ) ); -$I->click( '#payment_method_wordpoints_points' ); $I->waitForElementVisible( '[name=wordpoints_points-points-type]' ); $I->selectOption( 'wordpoints_points-points-type', 'Test' ); $I->click( 'Place order' ); diff --git a/tests/phpunit/test-uninstall.php b/tests/phpunit/test-uninstall.php index 87fdc4c..992761f 100644 --- a/tests/phpunit/test-uninstall.php +++ b/tests/phpunit/test-uninstall.php @@ -12,7 +12,7 @@ * * @since 1.0.0 * - * @covers WordPoints_WooCommerce_Installable + * @coversNothing */ class WordPoints_WooCommerce_Uninstall_Test extends WordPoints_PHPUnit_TestCase_Extension_Uninstall { diff --git a/tests/phpunit/tests/classes/hook/event/order/complete.php b/tests/phpunit/tests/classes/hook/event/order/complete.php index a667dc2..bf80583 100644 --- a/tests/phpunit/tests/classes/hook/event/order/complete.php +++ b/tests/phpunit/tests/classes/hook/event/order/complete.php @@ -95,6 +95,7 @@ protected function refund_order( $order_id ) { $_POST['order_id'] = $order->get_id(); $_POST['refund_amount'] = $order->get_total(); + $_POST['refunded_amount'] = '0.00'; $_POST['refund_reason'] = 'Testing.'; $_POST['line_item_qtys'] = '[]'; $_POST['line_item_totals'] = '[]'; diff --git a/tests/phpunit/tests/points/coupons.php b/tests/phpunit/tests/points/coupons.php new file mode 100644 index 0000000..a4b3284 --- /dev/null +++ b/tests/phpunit/tests/points/coupons.php @@ -0,0 +1,109 @@ +factory->user->create(); + + wp_set_current_user( $user_id ); + + // Give the user some points. + wordpoints_set_points( $user_id, 5, 'points', 'test' ); + + $this->assertSame( 5, wordpoints_get_points( $user_id, 'points' ) ); + + // Create the order. + $order = WC_Helper_Order::create_order( $user_id ); + + // Create the coupon. + $coupon = WC_Helper_Coupon::create_coupon( 'fake-coupon' ); + $coupon->set_amount( 10 ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_points_type', 'points' ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_cost', 10 ); + $coupon->save(); + + // Apply the coupon. + $result = $order->apply_coupon( $coupon ); + + $this->assertWPError( $result ); + $this->assertSame( + 'This coupon costs $10pts., you have only $5pts..' + , $result->get_error_message() + ); + } + + /** + * Tests using a points coupon on an order. + * + * @since 1.3.0 + * + * @covers ::wordpoints_woocommerce_points_coupons_apply + */ + public function test_coupon_apply() { + + $user_id = $this->factory->user->create(); + + wp_set_current_user( $user_id ); + + // Give the user some points. + wordpoints_set_points( $user_id, 100, 'points', 'test' ); + + $this->assertSame( 100, wordpoints_get_points( $user_id, 'points' ) ); + + // Create the order. + $order = WC_Helper_Order::create_order( $user_id ); + + $this->assertSame( '50.00', $order->get_total() ); + + // Create the coupon. + $coupon = WC_Helper_Coupon::create_coupon( 'fake-coupon' ); + $coupon->set_amount( 5 ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_points_type', 'points' ); + $coupon->update_meta_data( 'wordpoints_woocommerce_points_coupon_cost', 10 ); + $coupon->save(); + + // Apply the coupon. + $order->apply_coupon( $coupon ); + $order->save(); + + // Check that the coupon is applied. + $this->assertSame( '45.00', $order->get_total() ); + + // Complete the order. + $order->set_status( 'completed' ); + $order->save(); + + // The points should have been deducted. + $this->assertSame( 90, wordpoints_get_points( $user_id, 'points' ) ); + + // Cancel the order. + $order->set_status( 'cancelled' ); + $order->save(); + + // The points should have been restored. + $this->assertSame( 100, wordpoints_get_points( $user_id, 'points' ) ); + } +} + +// EOF diff --git a/tests/phpunit/tests/points/gateways/points.php b/tests/phpunit/tests/points/gateways/points.php index 0a66761..2b475f0 100644 --- a/tests/phpunit/tests/points/gateways/points.php +++ b/tests/phpunit/tests/points/gateways/points.php @@ -503,23 +503,14 @@ protected function simulate_checkout( array $args = array() ) { $messages = ob_get_clean(); if ( ! empty( $args['expected_errors'] ) ) { - - $expected_errors = '
  • ' - . implode( '', (array) $args['expected_errors'] ) - . '
  • ' . "\n"; - + $expected_errors = $args['expected_errors']; } else { - - $expected_errors = - '
  • WordPoints_WooCommerce_Points_Gateway_Test
  • ' - . "\n"; - + $expected_errors = 'WordPoints_WooCommerce_Points_Gateway_Test'; } - $this->assertSame( - '
      ' . "\n" . $expected_errors . '
    ' - , trim( $messages ) - ); + foreach ( (array) $expected_errors as $expected_error ) { + $this->assertStringContains( $expected_error, $messages ); + } } /**