diff --git a/changelog.txt b/changelog.txt index ff63e1e..2b1c0ea 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,39 @@ *** WooCommerce Subscriptions Changelog *** +2018.01.29 - version 2.2.17 +* Fix: Use line item meta values in a WC version compatible way. PR#2469 +* Fix: Only log cache manager messages if the logger has been loaded. PR#2468 +* Fix: Init `WC()->mailer()` to make sure our retry email classes are available and loaded. PR#2476 +* Fix: Update calls to 'woocommerce_payment_complete_order_status' to include order as 3rd argument. PR#2503 +* Fix: Prevent wp_kses from stripping off the allow_clear attribute of Select2 elements. PR#2354 +* Fix: Update `woocommerce_cart_totals_coupon_html` filter to include an extra arg introduced in WC 3.0. PR#2447 +* Fix: Ensure the Upcoming Revenue report correctly calculates revenue in periods beyond the next payment date. PR#2461 +* Fix: [WC3.2] Print the order downloads table in renewal order emails. PR#2522 +* Fix: [WC3.3] Replace uses of deprecated `woocommerce_params` with 'woocommerce_get_script_data'. PR#2509 +* Fix: [WC3.3] Get the active set of PayPal API credential depending on the gateway mode. PR#2514 +* Fix: [WC3.3] Display the filter by customer on the admin subscriptions table. PR#2521 +* Tweak: Add Subscriptions template overrides to system status. PR#2422 +* Tweak: Do not empty cart when adding additional units of a subscription product when mixed checkout is disabled. PR#2464 +* Tweak: Trigger a `do_action` when customers remove and readd line items to their subscription. PR#2458 +* Tweak: Untrash subscriptions when parent order is untrashed. PR#2472 +* Tweak: Skip the My Account Subscriptions table, linking directly to the subscription if customer only has one. PR#2474 +* Tweak: Change the subscription product length/expiration label from 'Expire after' to Subscription length. PR#2483 +* Tweak: Reword the Mixed Checkout setting description. PR#2490 +* Tweak: Cache user subscriptions to improve performance of limitation queries and user API functions. PR#2388 +* Tweak: Pass the `$number` parameter to callbacks hooked onto `woocommerce_subscription_periods` and `woocommerce_subscription_trial_periods`. PR#2520 + +2017.12.07 - version 2.2.16 +* Fix: Update PayPal doc links. PR#2414 +* Fix: [WC3.2] Make sure Subscriptions checks if the PayPal credentials have changed on correct hook. PR#2430 +* Fix: Only allow subscription line items to be removed from the My Account page when they should be allowed. PR#2440 +* Fix: Fix subscription ranges transient key when user has different language/locale than site default. PR#2286 +* Fix: Replace use of deprecated function with wc_get_cart_url(). PR#2442 +* Fix: Force payment method selection when switching when manual renewals are disabled. PR#2348 +* Tweak: Deprecate WC_Subscriptions::add_notice() and WC_Subscriptions::print_notices(). PR#2453 +* Tweak: Display "Free" for shipping that costs 0 only. PR#2437 +* Tweak: Format PayPal totals as strings when comparing. PR#2271 +* Tweak: Improve recurring fee support. PR#2462 + 2017.11.07 - version 2.2.15 * Fix: Don't include auto-draft subscriptions in the current subscriptions count in reports. PR#2414 * Fix: Reset cart item prorated amounts between calculations to avoid displaying double prorated prices. PR#2413 diff --git a/composer.lock b/composer.lock index 451c48c..51054e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "f940d0fcab30dd0d30d8b2234dbbed1b", + "content-hash": "021c140a843bd5b969f18f6e06902332", "packages": [ { "name": "composer/installers", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045" + "reference": "9ce17fb70e9a38dd8acff0636a29f5cf4d575c1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/79ad876c7498c0bbfe7eed065b8651c93bfd6045", - "reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045", + "url": "https://api.github.com/repos/composer/installers/zipball/9ce17fb70e9a38dd8acff0636a29f5cf4d575c1b", + "reference": "9ce17fb70e9a38dd8acff0636a29f5cf4d575c1b", "shasum": "" }, "require": { @@ -63,6 +63,7 @@ "Hurad", "ImageCMS", "Kanboard", + "Lan Management System", "MODX Evo", "Mautic", "Maya", @@ -86,6 +87,7 @@ "croogo", "dokuwiki", "drupal", + "eZ Platform", "elgg", "expressionengine", "fuelphp", @@ -102,6 +104,7 @@ "mediawiki", "modulework", "moodle", + "osclass", "phpbb", "piwik", "ppi", @@ -118,10 +121,1127 @@ "zend", "zikula" ], - "time": "2017-04-24T06:37:16+00:00" + "time": "2017-08-09T07:53:48+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27T11:43:31+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.3.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-08-08T06:39:58+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773", + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-06-03T08:32:36+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-03-02T20:05:34+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03T07:40:28+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-02-27T10:12:30+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed", + "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-07-23T12:43:26+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], diff --git a/includes/admin/class-wc-subscriptions-admin.php b/includes/admin/class-wc-subscriptions-admin.php index 58fede2..ac6cacb 100644 --- a/includes/admin/class-wc-subscriptions-admin.php +++ b/includes/admin/class-wc-subscriptions-admin.php @@ -231,7 +231,7 @@ class WC_Subscriptions_Admin { woocommerce_wp_select( array( 'id' => '_subscription_length', 'class' => 'wc_input_subscription_length select short', - 'label' => __( 'Subscription length', 'woocommerce-subscriptions' ), + 'label' => __( 'Expire after', 'woocommerce-subscriptions' ), 'options' => wcs_get_subscription_ranges( $chosen_period ), 'desc_tip' => true, 'description' => __( 'Automatically expire the subscription after this length of time. This length is in addition to any free trial or amount of time provided before a synchronised first renewal date.', 'woocommerce-subscriptions' ), @@ -354,7 +354,7 @@ class WC_Subscriptions_Admin { - + @@ -1142,11 +1142,11 @@ class WC_Subscriptions_Admin { array( 'name' => __( 'Mixed Checkout', 'woocommerce-subscriptions' ), - 'desc' => __( 'Allow subscriptions and products to be purchased simultaneously.', 'woocommerce-subscriptions' ), + 'desc' => __( 'Allow multiple subscriptions and products to be purchased simultaneously.', 'woocommerce-subscriptions' ), 'id' => self::$option_prefix . '_multiple_purchase', 'default' => 'no', 'type' => 'checkbox', - 'desc_tip' => __( 'Allow subscriptions and products to be purchased in a single transaction.', 'woocommerce-subscriptions' ), + 'desc_tip' => __( 'Allow a subscription product to be purchased with other products and subscriptions in the same transaction.', 'woocommerce-subscriptions' ), ), array( @@ -1419,6 +1419,14 @@ class WC_Subscriptions_Admin { 'success' => ( WC_Subscriptions::is_duplicate_site() ) ? 0 : 1, ); + $theme_overrides = self::get_theme_overrides(); + $debug_data['wcs_theme_overrides'] = array( + 'name' => _x( 'Subscriptions Template Theme Overrides', 'label for the system status page', 'woocommerce-subscriptions' ), + 'mark' => '', + 'mark_icon' => $theme_overrides['has_outdated_templates'] ? 'warning' : 'yes', + 'data' => $theme_overrides, + ); + $debug_data = apply_filters( 'wcs_system_status', $debug_data ); include( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/admin/status.php' ); @@ -1665,6 +1673,57 @@ class WC_Subscriptions_Admin { return apply_filters( 'wcs_admin_is_subscription_product_save_request', $is_subscription_product_save_request, $post_id, $product_types ); } + /** + * Determine which of our files have been overridden by the theme. + * + * @author Jeremy Pry + * @return array Theme override data. + */ + private static function get_theme_overrides() { + $wcs_template_dir = dirname( WC_Subscriptions::$plugin_file ) . '/templates/'; + $wc_template_path = trailingslashit( wc()->template_path() ); + $theme_root = trailingslashit( get_theme_root() ); + $overridden = array(); + $outdated = false; + $templates = WC_Admin_Status::scan_template_files( $wcs_template_dir ); + + foreach ( $templates as $file ) { + $theme_file = $is_outdated = false; + $locations = array( + get_stylesheet_directory() . "/{$file}", + get_stylesheet_directory() . "/{$wc_template_path}{$file}", + get_template_directory() . "/{$file}", + get_template_directory() . "/{$wc_template_path}{$file}", + ); + + foreach ( $locations as $location ) { + if ( is_readable( $location ) ) { + $theme_file = $location; + break; + } + } + + if ( ! empty( $theme_file ) ) { + $core_version = WC_Admin_Status::get_file_version( $wcs_template_dir . $file ); + $theme_version = WC_Admin_Status::get_file_version( $theme_file ); + if ( $core_version && ( empty( $theme_version ) || version_compare( $theme_version, $core_version, '<' ) ) ) { + $outdated = $is_outdated = true; + } + $overridden[] = array( + 'file' => str_replace( $theme_root, '', $theme_file ), + 'version' => $theme_version, + 'core_version' => $core_version, + 'is_outdated' => $is_outdated, + ); + } + } + + return array( + 'has_outdated_templates' => $outdated, + 'overridden_templates' => $overridden, + ); + } + /** * Outputs the contents of the "Renewal Orders" meta box. * diff --git a/includes/admin/class-wcs-admin-post-types.php b/includes/admin/class-wcs-admin-post-types.php index d410692..14347e7 100644 --- a/includes/admin/class-wcs-admin-post-types.php +++ b/includes/admin/class-wcs-admin-post-types.php @@ -51,6 +51,7 @@ class WCS_Admin_Post_Types { add_action( 'restrict_manage_posts', array( $this, 'restrict_by_product' ) ); add_action( 'restrict_manage_posts', array( $this, 'restrict_by_payment_method' ) ); + add_action( 'restrict_manage_posts', array( $this, 'restrict_by_customer' ) ); add_action( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 ); add_filter( 'post_row_actions', array( $this, 'shop_subscription_row_actions' ), 10, 2 ); @@ -1092,6 +1093,41 @@ class WCS_Admin_Post_Types { return $item_html; } + + /** + * Renders the dropdown for the customer filter. + * + * @since 2.2.17 + */ + public static function restrict_by_customer() { + global $typenow; + + // Prior to WC 3.3 this was handled by WC core so exit early if an earlier version of WC is active. + if ( 'shop_subscription' !== $typenow || WC_Subscriptions::is_woocommerce_pre( '3.3' ) ) { + return; + } + + $user_string = ''; + $user_id = ''; + + if ( ! empty( $_GET['_customer_user'] ) ) { + $user_id = absint( $_GET['_customer_user'] ); + $user = get_user_by( 'id', $user_id ); + + $user_string = sprintf( + /* translators: 1: user display name 2: user ID 3: user email */ + esc_html__( '%1$s (#%2$s – %3$s)', 'woocommerce-subscriptions' ), + $user->display_name, + absint( $user->ID ), + $user->user_email + ); + } + ?> + + mailer(); + $retries = WCS_Retry_Manager::store()->get_retries_for_order( $post->ID ); include_once( 'views/html-retries-table.php' ); diff --git a/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php b/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php index c1dfab0..89524f0 100644 --- a/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php +++ b/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php @@ -30,11 +30,6 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { foreach ( $this->order_ids_recurring_totals as $r ) { - if ( strtotime( $r->scheduled_date ) >= $this->start_date ) { - $total_renewal_revenue += $r->recurring_total; - $total_renewal_count += $r->total_renewals; - } - $subscription_ids = explode( ',', $r->subscription_ids ); $billing_intervals = explode( ',', $r->billing_intervals ); $billing_periods = explode( ',', $r->billing_periods ); @@ -57,7 +52,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { $next_payment_timestamp = wcs_add_time( $billing_intervals[ $key ], $billing_periods[ $key ], $next_payment_timestamp ); // If there are more renewals add them to the existing object or create a new one - if ( $next_payment_timestamp < $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) { + if ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) { $update_key = date( 'Y-m-d', $next_payment_timestamp ); if ( $next_payment_timestamp >= $this->start_date ) { @@ -70,11 +65,18 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { } $this->order_ids_recurring_totals[ $update_key ]->total_renewals += 1; $this->order_ids_recurring_totals[ $update_key ]->recurring_total += $subscription_totals[ $key ]; - $total_renewal_revenue += $subscription_totals[ $key ];; - $total_renewal_count += 1; } } - } while ( $next_payment_timestamp < $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ); + } while ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ); + } + } + + // Sum up the total revenue and total renewal count separately to avoid adding up multiple times. + foreach ( $this->order_ids_recurring_totals as $r ) { + if ( strtotime( $r->scheduled_date ) >= $this->start_date && strtotime( $r->scheduled_date ) <= $this->end_date ) { + + $total_renewal_revenue += $r->recurring_total ; + $total_renewal_count += $r->total_renewals; } } @@ -140,13 +142,14 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { AND p.post_status = 'wc-active' AND mo.meta_key = '_order_total' AND ms.meta_key = '_schedule_next_payment' - AND ms.meta_value BETWEEN '%s' AND '%s' + AND ( ( ms.meta_value < '%s' AND me.meta_value = 0 ) OR ( me.meta_value > '%s' AND ms.meta_value < '%s' ) ) AND mi.meta_key = '_billing_interval' AND mp.meta_key = '_billing_period' AND me.meta_key = '_schedule_end ' GROUP BY {$this->group_by_query} ORDER BY ms.meta_value ASC", '%Y-%m-%d', + date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ), date( 'Y-m-d', $this->start_date ), date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ) ); @@ -364,7 +367,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { } // 3 months max for day view - if ( $interval > 3 ) { + if ( $interval >= 3 ) { $this->chart_groupby = 'month'; } else { $this->chart_groupby = 'day'; @@ -400,7 +403,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { $this->barwidth = 60 * 60 * 24 * 1000; break; case 'month' : - $this->group_by_query = 'YEAR(ms.meta_value), MONTH(ms.meta_value)'; + $this->group_by_query = 'YEAR(ms.meta_value), MONTH(ms.meta_value), DAY(ms.meta_value)'; $this->chart_interval = 0; $min_date = $this->start_date; while ( ( $min_date = wcs_add_months( $min_date, '1' ) ) <= $this->end_date ) { diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php index bbef503..ff4609a 100644 --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -628,7 +628,7 @@ class WC_Subscription extends WC_Order { 'wc-completed', ); - $custom_status = apply_filters( 'woocommerce_payment_complete_order_status', 'completed', $this->get_id() ); + $custom_status = apply_filters( 'woocommerce_payment_complete_order_status', 'completed', $this->get_id(), $this ); if ( '' !== $custom_status && ! in_array( $custom_status, $paid_statuses ) && ! in_array( 'wc-' . $custom_status, $paid_statuses ) ) { $paid_statuses[] = $custom_status; diff --git a/includes/class-wc-subscriptions-cart.php b/includes/class-wc-subscriptions-cart.php index cb26cb0..d2b049f 100644 --- a/includes/class-wc-subscriptions-cart.php +++ b/includes/class-wc-subscriptions-cart.php @@ -122,6 +122,8 @@ class WC_Subscriptions_Cart { add_filter( 'woocommerce_shipping_free_shipping_is_available', __CLASS__ . '::maybe_recalculate_shipping_method_availability', 10, 2 ); add_filter( 'woocommerce_add_to_cart_handler', __CLASS__ . '::add_to_cart_handler', 10, 2 ); + + add_action( 'woocommerce_cart_calculate_fees', __CLASS__ . '::apply_recurring_fees', 1000, 1 ); } /** @@ -1261,6 +1263,29 @@ class WC_Subscriptions_Cart { return $is_available; } + /** + * Allow third-parties to apply fees which apply to the cart to recurring carts. + * + * @param WC_Cart + * @since 2.2.16 + */ + public static function apply_recurring_fees( $cart ) { + + if ( ! empty( $cart->recurring_cart_key ) ) { + + foreach ( WC()->cart->get_fees() as $fee ) { + + if ( apply_filters( 'woocommerce_subscriptions_is_recurring_fee', false, $fee, $cart ) ) { + if ( is_callable( array( $cart, 'fees_api' ) ) ) { // WC 3.2 + + $cart->fees_api()->add_fee( $fee ); + } else { + $cart->add_fee( $fee->name, $fee->amount, $fee->taxable, $fee->tax_class ); + } + } + } + } + } + /* Deprecated */ /** diff --git a/includes/class-wc-subscriptions-change-payment-gateway.php b/includes/class-wc-subscriptions-change-payment-gateway.php index 41c2e10..f93e8fd 100644 --- a/includes/class-wc-subscriptions-change-payment-gateway.php +++ b/includes/class-wc-subscriptions-change-payment-gateway.php @@ -194,13 +194,13 @@ class WC_Subscriptions_Change_Payment_Gateway { if ( ! empty( self::$woocommerce_errors ) ) { foreach ( self::$woocommerce_errors as $error ) { - WC_Subscriptions::add_notice( $error, 'error' ); + wc_add_notice( $error, 'error' ); } } if ( ! empty( self::$woocommerce_messages ) ) { foreach ( self::$woocommerce_messages as $message ) { - WC_Subscriptions::add_notice( $message, 'success' ); + wc_add_notice( $message, 'success' ); } } @@ -208,19 +208,19 @@ class WC_Subscriptions_Change_Payment_Gateway { if ( wp_verify_nonce( $_GET['_wpnonce'] ) === false ) { - WC_Subscriptions::add_notice( __( 'There was an error with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'There was an error with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' ); } elseif ( empty( $subscription ) ) { - WC_Subscriptions::add_notice( __( 'Invalid Subscription.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'Invalid Subscription.', 'woocommerce-subscriptions' ), 'error' ); } elseif ( ! current_user_can( 'edit_shop_subscription_payment_method', $subscription->get_id() ) ) { - WC_Subscriptions::add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' ); } elseif ( ! $subscription->can_be_updated_to( 'new-payment-method' ) ) { - WC_Subscriptions::add_notice( __( 'The payment method can not be changed for that subscription.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'The payment method can not be changed for that subscription.', 'woocommerce-subscriptions' ), 'error' ); } else { @@ -232,8 +232,8 @@ class WC_Subscriptions_Change_Payment_Gateway { } // translators: placeholder is either empty or "Next payment is due..." - WC_Subscriptions::add_notice( sprintf( __( 'Choose a new payment method.%s', 'woocommerce-subscriptions' ), $next_payment_string ), 'notice' ); - WC_Subscriptions::print_notices(); + wc_add_notice( sprintf( __( 'Choose a new payment method.%s', 'woocommerce-subscriptions' ), $next_payment_string ), 'notice' ); + wc_print_notices(); if ( $subscription->get_order_key() == $_GET['key'] ) { @@ -261,14 +261,14 @@ class WC_Subscriptions_Change_Payment_Gateway { } else { - WC_Subscriptions::add_notice( __( 'Invalid order.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'Invalid order.', 'woocommerce-subscriptions' ), 'error' ); } } } if ( false === $valid_request ) { - WC_Subscriptions::print_notices(); + wc_print_notices(); } } @@ -363,7 +363,7 @@ class WC_Subscriptions_Change_Payment_Gateway { // Redirect to success/confirmation/payment page if ( 'success' == $result['result'] ) { - WC_Subscriptions::add_notice( __( 'Payment method updated.', 'woocommerce-subscriptions' ), 'success' ); + wc_add_notice( __( 'Payment method updated.', 'woocommerce-subscriptions' ), 'success' ); wp_redirect( $result['redirect'] ); exit; } diff --git a/includes/class-wc-subscriptions-checkout.php b/includes/class-wc-subscriptions-checkout.php index 960b916..224d00c 100644 --- a/includes/class-wc-subscriptions-checkout.php +++ b/includes/class-wc-subscriptions-checkout.php @@ -32,9 +32,8 @@ class WC_Subscriptions_Checkout { // Restore the settings after switching them for the checkout form add_action( 'woocommerce_after_checkout_form', __CLASS__ . '::restore_checkout_registration_settings', 100 ); - // Make sure guest checkout is not enabled in option param passed to WC JS - add_filter( 'woocommerce_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 ); - add_filter( 'wc_checkout_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 ); + // Some callbacks need to hooked after WC has loaded. + add_action( 'woocommerce_loaded', array( __CLASS__, 'attach_dependant_hooks' ) ); // Force registration during checkout process add_action( 'woocommerce_before_checkout_process', __CLASS__ . '::force_registration_during_checkout', 10 ); @@ -43,6 +42,19 @@ class WC_Subscriptions_Checkout { add_action( 'woocommerce_checkout_create_order_line_item', __CLASS__ . '::remove_backorder_meta_from_subscription_line_item', 10, 4 ); } + /** + * @since 2.2.17 + */ + public static function attach_dependant_hooks() { + // Make sure guest checkout is not enabled in option param passed to WC JS + if ( WC_Subscriptions::is_woocommerce_pre( '3.3' ) ) { + add_filter( 'woocommerce_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 ); + add_filter( 'wc_checkout_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 ); + } else { + add_filter( 'woocommerce_get_script_data', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 2 ); + } + } + /** * Create subscriptions purchased on checkout. * @@ -445,7 +457,11 @@ class WC_Subscriptions_Checkout { * * @since 1.1 */ - public static function filter_woocommerce_script_paramaters( $woocommerce_params ) { + public static function filter_woocommerce_script_paramaters( $woocommerce_params, $handle = '' ) { + // WC 3.3+ deprecates handle-specific filters in favor of 'woocommerce_get_script_data'. + if ( 'woocommerce_get_script_data' === current_filter() && ! in_array( $handle, array( 'woocommerce', 'wc-checkout' ) ) ) { + return $woocommerce_params; + } if ( WC_Subscriptions_Cart::cart_contains_subscription() && ! is_user_logged_in() && isset( $woocommerce_params['option_guest_checkout'] ) && 'yes' == $woocommerce_params['option_guest_checkout'] ) { $woocommerce_params['option_guest_checkout'] = 'no'; diff --git a/includes/class-wc-subscriptions-email.php b/includes/class-wc-subscriptions-email.php index f759568..c00cb09 100644 --- a/includes/class-wc-subscriptions-email.php +++ b/includes/class-wc-subscriptions-email.php @@ -26,6 +26,7 @@ class WC_Subscriptions_Email { add_filter( 'woocommerce_resend_order_emails_available', __CLASS__ . '::renewal_order_emails_available', -1 ); // run before other plugins so we don't remove their emails + add_action( 'woocommerce_subscriptions_email_order_details', __CLASS__ . '::order_download_details', 10, 4 ); add_action( 'woocommerce_subscriptions_email_order_details', __CLASS__ . '::order_details', 10, 4 ); } @@ -343,6 +344,21 @@ class WC_Subscriptions_Email { } } + /** + * With WooCommerce 3.2+ active display the downloads table. + * + * @param WC_Order $order + * @param bool $sent_to_admin Whether the email is sent to admin - defaults to false + * @param bool $plain_text Whether the email should use plain text templates - defaults to false + * @param WC_Email $email + * @since 2.2.17 + */ + public static function order_download_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) { + if ( is_callable( array( 'WC_Emails', 'order_downloads' ) ) ) { + WC_Emails::instance()->order_downloads( $order, $sent_to_admin, $plain_text, $email ); + } + } + /** * Init the mailer and call the notifications for the current filter. * diff --git a/includes/class-wc-subscriptions-manager.php b/includes/class-wc-subscriptions-manager.php index 682f443..1176b2c 100644 --- a/includes/class-wc-subscriptions-manager.php +++ b/includes/class-wc-subscriptions-manager.php @@ -50,6 +50,9 @@ class WC_Subscriptions_Manager { // Order is trashed, trash subscription add_action( 'wp_trash_post', __CLASS__ . '::maybe_trash_subscription', 10 ); + // Order is restored (untrashed), restore subscription + add_action( 'untrashed_post', __CLASS__ . '::maybe_untrash_subscription', 10 ); + // When order is deleted, delete the subscription. add_action( 'before_delete_post', array( __CLASS__, 'maybe_delete_subscription' ) ); @@ -792,6 +795,19 @@ class WC_Subscriptions_Manager { } } + /** + * Untrash all subscriptions attached to an order when it's restored. + * @param int $post_id The post ID of the WC Order being restored + * @since 2.2.17 + */ + public static function maybe_untrash_subscription( $post_id ) { + if ( 'shop_order' == get_post_type( $post_id ) ) { + foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'parent', 'subscription_status' => array( 'trash' ) ) ) as $subscription ) { + wp_untrash_post( $subscription->get_id() ); + } + } + } + /** * Delete related subscriptions when an order is deleted. * diff --git a/includes/class-wc-subscriptions-order.php b/includes/class-wc-subscriptions-order.php index f7e3fac..92c2120 100644 --- a/includes/class-wc-subscriptions-order.php +++ b/includes/class-wc-subscriptions-order.php @@ -477,7 +477,7 @@ class WC_Subscriptions_Order { $subscriptions = wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => 'parent' ) ); $was_activated = false; $order = wc_get_order( $order_id ); - $order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) ); + $order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id, $order ), 'processing', 'completed' ) ) && in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) ); foreach ( $subscriptions as $subscription ) { diff --git a/includes/class-wc-subscriptions-renewal-order.php b/includes/class-wc-subscriptions-renewal-order.php index 566a109..b1afe47 100644 --- a/includes/class-wc-subscriptions-renewal-order.php +++ b/includes/class-wc-subscriptions-renewal-order.php @@ -80,7 +80,7 @@ class WC_Subscriptions_Renewal_Order { $subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id ); $was_activated = false; $order = wc_get_order( $order_id ); - $order_completed = in_array( $orders_new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ); + $order_completed = in_array( $orders_new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id, $order ), 'processing', 'completed' ) ); $order_needed_payment = in_array( $orders_old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) ); if ( $order_completed && $order_needed_payment ) { diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php index 77e6c61..952429d 100644 --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -176,7 +176,7 @@ class WC_Subscriptions_Switcher { $switch_message = __( 'Choose a new subscription.', 'woocommerce-subscriptions' ); } - WC_Subscriptions::add_notice( $switch_message, 'notice' ); + wc_add_notice( $switch_message, 'notice' ); } } elseif ( ( is_cart() || is_checkout() ) && ! is_order_received_page() && false !== ( $switch_items = self::cart_contains_switches() ) ) { @@ -195,9 +195,9 @@ class WC_Subscriptions_Switcher { } if ( $removed_item_count > 0 ) { - WC_Subscriptions::add_notice( _n( 'Your cart contained an invalid subscription switch request. It has been removed.', 'Your cart contained invalid subscription switch requests. They have been removed.', $removed_item_count, 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( _n( 'Your cart contained an invalid subscription switch request. It has been removed.', 'Your cart contained invalid subscription switch requests. They have been removed.', $removed_item_count, 'woocommerce-subscriptions' ), 'error' ); - wp_redirect( WC()->cart->get_cart_url() ); + wp_redirect( wc_get_cart_url() ); exit(); } } elseif ( is_product() && $product = wc_get_product( $post ) ) { // Automatically initiate the switch process for limited variable subscriptions @@ -249,7 +249,7 @@ class WC_Subscriptions_Switcher { $subscribed_notice = sprintf( __( '%1$s Complete payment on %2$sOrder %3$s%4$s to be able to change your subscription.', 'woocommerce-subscriptions' ), $subscribed_notice, sprintf( '', $last_order->get_checkout_payment_url() ), $last_order->get_order_number(), '' ); } - WC_Subscriptions::add_notice( $subscribed_notice, 'notice' ); + wc_add_notice( $subscribed_notice, 'notice' ); break; } else { @@ -1017,7 +1017,7 @@ class WC_Subscriptions_Switcher { $subscription_switches[ $cart_item_key ] = $cart_item['subscription_switch']; } else { WC()->cart->remove_cart_item( $cart_item_key ); - WC_Subscriptions::add_notice( __( 'Your cart contained an invalid subscription switch request. It has been removed.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'Your cart contained an invalid subscription switch request. It has been removed.', 'woocommerce-subscriptions' ), 'error' ); } } } @@ -1164,7 +1164,7 @@ class WC_Subscriptions_Switcher { // Requesting a switch for someone elses subscription if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) { - WC_Subscriptions::add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' ); WC()->cart->empty_cart( true ); wp_redirect( get_permalink( $subscription['product_id'] ) ); exit(); @@ -1205,7 +1205,7 @@ class WC_Subscriptions_Switcher { } catch ( Exception $e ) { - WC_Subscriptions::add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' ); WC()->cart->empty_cart( true ); wp_redirect( get_permalink( wc_get_page_id( 'cart' ) ) ); exit(); @@ -1682,7 +1682,7 @@ class WC_Subscriptions_Switcher { return; } - $order_completed = in_array( $order_new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ); + $order_completed = in_array( $order_new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id, $order ), 'processing', 'completed' ) ); if ( $order_completed ) { try { @@ -2047,7 +2047,16 @@ class WC_Subscriptions_Switcher { // Check that the existing subscriptions are for $0 recurring $old_recurring_total = $subscription->get_total(); - if ( 0 == $old_recurring_total && $new_recurring_total > 0 && true === $has_future_payments && $subscription->is_manual() ) { + // Check for $0 / period to a non-zero $ / period and manual subscription + $switch_from_zero_manual_subscription = ( 0 == $old_recurring_total && $subscription->is_manual() ); + + // Check for manual renewals accepted, in case of automatic subscription switch with no proration + $accept_manual_renewals = ( 'yes' == get_option( WC_Subscriptions_Admin::$option_prefix . '_accept_manual_renewals', 'no' ) ); + + // Check if old subscription is automatic + $old_subscription_automatic = ! $subscription->is_manual(); + + if ( ( $switch_from_zero_manual_subscription || ! $accept_manual_renewals || ( $accept_manual_renewals && $old_subscription_automatic ) ) && $new_recurring_total > 0 && true === $has_future_payments ) { WC()->cart->cart_contents[ $cart_item_key ]['subscription_switch']['force_payment'] = true; } } @@ -2234,7 +2243,6 @@ class WC_Subscriptions_Switcher { * @param WC_Order $order the switch order * @param WC_Subscription $subscription the subscription being switched * @since 2.1.2 - * TODO Remove this function in 2.1.n - compatibility code for 2.1 - 2.1.2 */ protected static function switch_line_items_pre_2_1_2( $switches, $order, $subscription ) { @@ -2288,7 +2296,6 @@ class WC_Subscriptions_Switcher { * @param WC_Subscription $subscription the subscription being switched * @param array $shipping_methods an array of shipping line items and meta * @since 2.1.2 - * TODO Remove this function in 2.1.n - compatibility code for 2.1 - 2.1.2 */ protected static function switch_shipping_line_items_pre_2_1_2( $subscription, $shipping_methods ) { // Archive the old subscription shipping methods diff --git a/includes/class-wcs-cached-data-manager.php b/includes/class-wcs-cached-data-manager.php index f76eec1..70d64ce 100644 --- a/includes/class-wcs-cached-data-manager.php +++ b/includes/class-wcs-cached-data-manager.php @@ -19,12 +19,16 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { add_action( 'trashed_post', array( $this, 'purge_delete' ), 9999 ); // trashed posts aren't included in 'any' queries add_action( 'untrashed_post', array( $this, 'purge_delete' ), 9999 ); // however untrashed posts are add_action( 'before_delete_post', array( $this, 'purge_delete' ), 9999 ); // if forced delete is enabled + add_action( 'update_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); add_action( 'updated_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys add_action( 'deleted_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys add_action( 'added_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys add_action( 'admin_init', array( $this, 'initialize_cron_check_size' ) ); // setup cron task to truncate big logs. add_filter( 'cron_schedules', array( $this, 'add_weekly_cron_schedule' ) ); // create a weekly cron schedule + + // Add actions to handle cache purge for users. + add_action( 'save_post', array( $this, 'purge_delete' ), 9999, 2 ); } /** @@ -40,7 +44,7 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { * @param string $message Message to log */ public function log( $message ) { - if ( defined( 'WCS_DEBUG' ) && WCS_DEBUG ) { + if ( is_object( $this->logger ) && defined( 'WCS_DEBUG' ) && WCS_DEBUG ) { $this->logger->add( 'wcs-cache', $message ); } } @@ -71,20 +75,29 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { /** * Clearing cache when a post is deleted * - * @param $post_id integer the ID of a post + * @param int $post_id The ID of a post + * @param WP_Post $post The post object (on certain hooks). */ - public function purge_delete( $post_id ) { - if ( 'shop_order' === get_post_type( $post_id ) ) { + public function purge_delete( $post_id, $post = null ) { + $post_type = get_post_type( $post_id ); + if ( 'shop_order' === $post_type ) { foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) ) as $subscription ) { $this->log( 'Calling purge delete on ' . current_filter() . ' for ' . $subscription->get_id() ); $this->clear_related_order_cache( $subscription ); } } - // Purge wcs_do_subscriptions_exist cache, but only on the before_delete_post hook. - if ( 'shop_subscription' === get_post_type( $post_id ) && doing_action( 'before_delete_post' ) ) { - $this->log( "Subscription {$post_id} deleted. Purging subscription cache." ); - $this->delete_cached( 'wcs_do_subscriptions_exist' ); + if ( 'shop_subscription' === $post_type ) { + // Purge wcs_do_subscriptions_exist cache, but only on the before_delete_post hook. + if ( doing_action( 'before_delete_post' ) ) { + $this->log( "Subscription {$post_id} deleted. Purging subscription cache." ); + $this->delete_cached( 'wcs_do_subscriptions_exist' ); + } + + // Purge cache for a specific user on the save_post hook. + if ( doing_action( 'save_post' ) ) { + $this->purge_subscription_user_cache( $post_id ); + } } } @@ -97,13 +110,37 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { * @param $meta_value mixed the ID of the subscription that relates to the order */ public function purge_from_metadata( $meta_id, $object_id, $meta_key, $meta_value ) { - if ( ! in_array( $meta_key, array( '_subscription_renewal', '_subscription_resubscribe', '_subscription_switch' ) ) || 'shop_order' !== get_post_type( $object_id ) ) { + static $combined_keys = null; + static $order_keys = array( + '_subscription_renewal' => 1, + '_subscription_resubscribe' => 1, + '_subscription_switch' => 1, + ); + static $subscription_keys = array( + '_customer_user' => 1, + ); + + if ( null === $combined_keys ) { + $combined_keys = array_merge( $order_keys, $subscription_keys ); + } + + // Ensure we're handling a meta key we actually care about. + if ( ! isset( $combined_keys[ $meta_key ] ) ) { return; } - $this->log( 'Calling purge from ' . current_filter() . ' on object ' . $object_id . ' and meta value ' . $meta_value . ' due to ' . $meta_key . ' meta key.' ); - - $this->clear_related_order_cache( $meta_value ); + if ( 'shop_order' === get_post_type( $object_id ) && isset( $order_keys[ $meta_key ] ) ) { + $this->log( sprintf( + 'Calling purge from %1$s on object %2$s and meta value %3$s due to %4$s meta key.', + current_filter(), + $object_id, + $meta_value, + $meta_key + ) ); + $this->clear_related_order_cache( $meta_value ); + } elseif ( 'shop_subscription' === get_post_type( $object_id ) && isset( $subscription_keys[ $meta_key ] ) ) { + $this->purge_subscription_user_cache( $object_id ); + } } /** @@ -133,13 +170,15 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { * Delete cached data with key * * @param string $key Key that needs deleting + * + * @return bool */ public function delete_cached( $key ) { if ( ! is_string( $key ) || empty( $key ) ) { - return; + return false; } - delete_transient( $key ); + return delete_transient( $key ); } /** @@ -206,6 +245,24 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { return $schedules; } + /** + * Purge the cache for the subscription's user. + * + * @author Jeremy Pry + * + * @param int $subscription_id The subscription to purge. + */ + protected function purge_subscription_user_cache( $subscription_id ) { + $subscription = wcs_get_subscription( $subscription_id ); + $subscription_user_id = $subscription->get_user_id(); + $this->log( sprintf( + 'Clearing cache for user ID %1$s on %2$s hook.', + $subscription_user_id, + current_action() + ) ); + $this->delete_cached( "wcs_user_subscriptions_{$subscription_user_id}" ); + } + /* Deprecated Functions */ /** diff --git a/includes/class-wcs-cart-resubscribe.php b/includes/class-wcs-cart-resubscribe.php index 79b4e82..6e92d2e 100644 --- a/includes/class-wcs-cart-resubscribe.php +++ b/includes/class-wcs-cart-resubscribe.php @@ -305,7 +305,7 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { public function maybe_cancel_existing_subscription( $order_id, $old_order_status, $new_order_status ) { if ( wcs_order_contains_subscription( $order_id ) && wcs_order_contains_resubscribe( $order_id ) ) { $order = wc_get_order( $order_id ); - $order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ); + $order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id, $order ), 'processing', 'completed' ) ); $order_needed_payment = in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) ); foreach ( wcs_get_subscriptions_for_resubscribe_order( $order_id ) as $subscription ) { diff --git a/includes/class-wcs-query.php b/includes/class-wcs-query.php index 2717ce9..7074f87 100644 --- a/includes/class-wcs-query.php +++ b/includes/class-wcs-query.php @@ -20,6 +20,7 @@ class WCS_Query extends WC_Query { // Inserting your new tab/page into the My Account page. add_filter( 'woocommerce_account_menu_items', array( $this, 'add_menu_items' ) ); + add_filter( 'woocommerce_get_endpoint_url', array( $this, 'maybe_redirect_to_only_subscription' ), 10, 2 ); add_action( 'woocommerce_account_subscriptions_endpoint', array( $this, 'endpoint_content' ) ); } @@ -109,17 +110,43 @@ class WCS_Query extends WC_Query { * @return array */ public function add_menu_items( $menu_items ) { + if ( 1 == count( wcs_get_users_subscriptions() ) ) { + $label = __( 'My Subscription', 'woocommerce-subscriptions' ); + } else { + $label = __( 'Subscriptions', 'woocommerce-subscriptions' ); + } // Add our menu item after the Orders tab if it exists, otherwise just add it to the end if ( array_key_exists( 'orders', $menu_items ) ) { - $menu_items = wcs_array_insert_after( 'orders', $menu_items, 'subscriptions', __( 'Subscriptions', 'woocommerce-subscriptions' ) ); + $menu_items = wcs_array_insert_after( 'orders', $menu_items, 'subscriptions', $label ); } else { - $menu_items['subscriptions'] = __( 'Subscriptions', 'woocommerce-subscriptions' ); + $menu_items['subscriptions'] = $label; } return $menu_items; } + /** + * Changes the URL for the subscriptions endpoint when there's only one user subscription. + * + * @since 2.2.17 + * @param string $url + * @param string $endpoint + * @return string + */ + public function maybe_redirect_to_only_subscription( $url, $endpoint ) { + if ( 'subscriptions' == $endpoint ) { + $subscriptions = wcs_get_users_subscriptions(); + + if ( 1 == count( $subscriptions ) ) { + $subscription = reset( $subscriptions ); + $url = $subscription->get_view_order_url(); + } + } + + return $url; + } + /** * Endpoint HTML content. */ diff --git a/includes/class-wcs-remove-item.php b/includes/class-wcs-remove-item.php index 56b3281..c79233d 100644 --- a/includes/class-wcs-remove-item.php +++ b/includes/class-wcs-remove-item.php @@ -102,6 +102,8 @@ class WCS_Remove_Item { // translators: 1$: product name, 2$: product id $subscription->add_order_note( sprintf( _x( 'Customer added "%1$s" (Product ID: #%2$d) via the My Account page.', 'used in order note', 'woocommerce-subscriptions' ), wcs_get_line_item_name( $line_item ), $product_id ) ); + do_action( 'wcs_user_readded_item', $line_item, $subscription ); + } else { wc_add_notice( __( 'Your request to undo your previous action was unsuccessful.', 'woocommerce-subscriptions' ) ); } @@ -125,6 +127,8 @@ class WCS_Remove_Item { // translators: placeholders are 1$: item name, and, 2$: opening and, 3$: closing link tags wc_add_notice( sprintf( __( 'You have successfully removed "%1$s" from your subscription. %2$sUndo?%3$s', 'woocommerce-subscriptions' ), $line_item['name'], '', '' ) ); + + do_action( 'wcs_user_removed_item', $line_item, $subscription ); } } diff --git a/includes/class-wcs-select2.php b/includes/class-wcs-select2.php index a0ca10e..5381df7 100644 --- a/includes/class-wcs-select2.php +++ b/includes/class-wcs-select2.php @@ -87,7 +87,7 @@ class WCS_Select2 { $allowed_attributes = array_map( array( $this, 'get_property_name' ), array_keys( $this->attributes ) ); $allowed_attributes = array_fill_keys( $allowed_attributes, array() ); - echo wp_kses( $this->get_html(), array( 'input' => $allowed_attributes, 'select' => $allowed_attributes, 'option' => $allowed_attributes ) ); + echo wp_kses_allow_underscores( $this->get_html(), array( 'input' => $allowed_attributes, 'select' => $allowed_attributes, 'option' => $allowed_attributes ) ); } /** diff --git a/includes/class-wcs-user-change-status-handler.php b/includes/class-wcs-user-change-status-handler.php index 2161b50..47f75ab 100644 --- a/includes/class-wcs-user-change-status-handler.php +++ b/includes/class-wcs-user-change-status-handler.php @@ -54,26 +54,26 @@ class WCS_User_Change_Status_Handler { if ( ! $subscription->needs_payment() ) { $subscription->update_status( $new_status ); $subscription->add_order_note( _x( 'Subscription reactivated by the subscriber from their account page.', 'order note left on subscription after user action', 'woocommerce-subscriptions' ) ); - WC_Subscriptions::add_notice( _x( 'Your subscription has been reactivated.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); + wc_add_notice( _x( 'Your subscription has been reactivated.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); $changed = true; } else { - WC_Subscriptions::add_notice( __( 'You can not reactivate that subscription until paying to renew it. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'You can not reactivate that subscription until paying to renew it. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); } break; case 'on-hold' : if ( wcs_can_user_put_subscription_on_hold( $subscription ) ) { $subscription->update_status( $new_status ); $subscription->add_order_note( _x( 'Subscription put on hold by the subscriber from their account page.', 'order note left on subscription after user action', 'woocommerce-subscriptions' ) ); - WC_Subscriptions::add_notice( _x( 'Your subscription has been put on hold.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); + wc_add_notice( _x( 'Your subscription has been put on hold.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); $changed = true; } else { - WC_Subscriptions::add_notice( __( 'You can not suspend that subscription - the suspension limit has been reached. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'You can not suspend that subscription - the suspension limit has been reached. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); } break; case 'cancelled' : $subscription->cancel_order(); $subscription->add_order_note( _x( 'Subscription cancelled by the subscriber from their account page.', 'order note left on subscription after user action', 'woocommerce-subscriptions' ) ); - WC_Subscriptions::add_notice( _x( 'Your subscription has been cancelled.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); + wc_add_notice( _x( 'Your subscription has been cancelled.', 'Notice displayed to user confirming their action.', 'woocommerce-subscriptions' ), 'success' ); $changed = true; break; } @@ -92,20 +92,20 @@ class WCS_User_Change_Status_Handler { $subscription = ( ! is_object( $subscription ) ) ? wcs_get_subscription( $subscription ) : $subscription; if ( ! wcs_is_subscription( $subscription ) ) { - WC_Subscriptions::add_notice( __( 'That subscription does not exist. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'That subscription does not exist. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); return false; } elseif ( ! empty( $wpnonce ) && wp_verify_nonce( $wpnonce, $subscription->get_id() . $subscription->get_status() ) === false ) { - WC_Subscriptions::add_notice( __( 'Security error. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'Security error. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); return false; } elseif ( ! user_can( $user_id, 'edit_shop_subscription_status', $subscription->get_id() ) ) { - WC_Subscriptions::add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' ); + wc_add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' ); return false; } elseif ( ! $subscription->can_be_updated_to( $new_status ) ) { // translators: placeholder is subscription's new status, translated - WC_Subscriptions::add_notice( sprintf( __( 'That subscription can not be changed to %s. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), wcs_get_subscription_status_name( $new_status ) ), 'error' ); + wc_add_notice( sprintf( __( 'That subscription can not be changed to %s. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), wcs_get_subscription_status_name( $new_status ) ), 'error' ); return false; } diff --git a/includes/gateways/paypal/class-wcs-paypal.php b/includes/gateways/paypal/class-wcs-paypal.php index 3cf5530..05725ed 100644 --- a/includes/gateways/paypal/class-wcs-paypal.php +++ b/includes/gateways/paypal/class-wcs-paypal.php @@ -111,6 +111,12 @@ class WCS_PayPal { * @since 2.0 */ public static function get_option( $setting_key ) { + + // Post WC 3.3 PayPal's sandbox and live API credentials are stored separately. When requesting the API keys make sure we return the active keys - live or sandbox depending on the mode. + if ( ! WC_Subscriptions::is_woocommerce_pre( '3.3' ) && in_array( $setting_key, array( 'api_username', 'api_password', 'api_signature' ) ) && 'yes' === self::get_option( 'testmode' ) ) { + $setting_key = 'sandbox_' . $setting_key; + } + return ( isset( self::$paypal_settings[ $setting_key ] ) ) ? self::$paypal_settings[ $setting_key ] : ''; } @@ -253,14 +259,14 @@ class WCS_PayPal { } else { - wp_safe_redirect( WC()->cart->get_cart_url() ); + wp_safe_redirect( wc_get_cart_url() ); } } catch ( Exception $e ) { wc_add_notice( __( 'An error occurred, please try again or try an alternate form of payment.', 'woocommerce-subscriptions' ), 'error' ); - wp_redirect( WC()->cart->get_cart_url() ); + wp_redirect( wc_get_cart_url() ); } exit; diff --git a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php index d16feb9..662f3ad 100644 --- a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php +++ b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php @@ -36,6 +36,9 @@ class WCS_PayPal_Admin { // Add the PayPal subscription information to the billing information add_action( 'woocommerce_admin_order_data_after_billing_address', __CLASS__ . '::profile_link' ); + + // Before WC updates the PayPal settings remove credentials error flag + add_action( 'load-woocommerce_page_wc-settings', __CLASS__ . '::maybe_update_credentials_error_flag', 9 ); } /** @@ -87,8 +90,6 @@ class WCS_PayPal_Admin { self::maybe_disable_invalid_profile_notice(); - self::maybe_update_credentials_error_flag(); - if ( ! in_array( get_woocommerce_currency(), apply_filters( 'woocommerce_paypal_supported_currencies', array( 'AUD', 'BRL', 'CAD', 'MXN', 'NZD', 'HKD', 'SGD', 'USD', 'EUR', 'JPY', 'TRY', 'NOK', 'CZK', 'DKK', 'HUF', 'ILS', 'MYR', 'PHP', 'PLN', 'SEK', 'CHF', 'TWD', 'THB', 'GBP', 'RMB' ) ) ) ) { $valid_for_use = false; } else { @@ -107,7 +108,7 @@ class WCS_PayPal_Admin { 'type' => 'warning', // translators: placeholders are opening and closing link tags. 1$-2$: to docs on woocommerce, 3$-4$ to gateway settings on the site 'text' => sprintf( esc_html__( 'PayPal is inactive for subscription transactions. Please %1$sset up the PayPal IPN%2$s and %3$senter your API credentials%4$s to enable PayPal for Subscriptions.', 'woocommerce-subscriptions' ), - '', + '', '', '', '' @@ -119,14 +120,13 @@ class WCS_PayPal_Admin { $notices[] = array( 'type' => 'warning', // translators: placeholders are opening and closing strong and link tags. 1$-2$: strong tags, 3$-8$ link to docs on woocommerce - 'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are not enabled on your account%2$s, some subscription management features are not enabled. Please contact PayPal and request they %3$senable PayPal Reference Transactions%4$s on your account. %5$sCheck PayPal Account%6$s %7$sLearn more %8$s', 'woocommerce-subscriptions' ), + 'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are not enabled on your account%2$s, some subscription management features are not enabled. Please contact PayPal and request they %3$senable PayPal Reference Transactions%4$s on your account. %5$sCheck PayPal Account%6$s %3$sLearn more %7$s', 'woocommerce-subscriptions' ), '', '', - '', + '', '', '
',
'',
- '',
'»'
),
);
@@ -218,7 +218,7 @@ class WCS_PayPal_Admin {
*
* @since 2.0
*/
- protected static function maybe_update_credentials_error_flag() {
+ public static function maybe_update_credentials_error_flag() {
// Check if the API credentials are being saved - we can't do this on the 'woocommerce_update_options_payment_gateways_paypal' hook because it is triggered after 'admin_notices'
if ( ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-settings' ) && isset( $_POST['woocommerce_paypal_api_username'] ) || isset( $_POST['woocommerce_paypal_api_password'] ) || isset( $_POST['woocommerce_paypal_api_signature'] ) ) {
diff --git a/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php b/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php
index 99aed88..757a276 100644
--- a/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php
+++ b/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php
@@ -284,8 +284,8 @@ class WCS_PayPal_Reference_Transaction_API_Request {
// calculate the total as PayPal would
$calculated_total += $this->round( $order_subtotal + $order->get_cart_tax() ) + $this->round( $order->get_total_shipping() + $order->get_shipping_tax() );
- // offset the discrepency between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal
- if ( $total_amount !== $calculated_total ) {
+ // offset the discrepancy between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal
+ if ( $this->price_format( $total_amount ) !== $this->price_format( $calculated_total ) ) {
$order_subtotal = $order_subtotal - ( $calculated_total - $total_amount );
}
@@ -334,12 +334,8 @@ class WCS_PayPal_Reference_Transaction_API_Request {
// add individual order items
foreach ( $order_items as $item ) {
$this->add_line_item_parameters( $item, $item_count++, $use_deprecated_params );
- $calculated_total += $this->round( $item['AMT'] * $item['QTY'] );
}
- // add shipping and tax to calculated total
- $calculated_total += $this->round( $order->get_total_shipping() ) + $this->round( $order->get_total_tax() );
-
$total_amount = $this->round( $order->get_total() );
// add order-level parameters
@@ -589,7 +585,7 @@ class WCS_PayPal_Reference_Transaction_API_Request {
// PayPal requires locale-specific number formats (e.g. USD is 123.45)
// PayPal requires the decimal separator to be a period (.)
- $this->parameters[ $key ] = number_format( $value, 2, '.', '' );
+ $this->parameters[ $key ] = $this->price_format( $value );
}
}
@@ -642,7 +638,7 @@ class WCS_PayPal_Reference_Transaction_API_Request {
$calculated_total += $this->round( $order->get_total_shipping() ) + $this->round( $order->get_total_tax() );
$total_amount = $this->round( $order->get_total() );
- if ( $total_amount !== $calculated_total ) {
+ if ( $this->price_format( $total_amount ) !== $this->price_format( $calculated_total ) ) {
$skip_line_items = true;
}
}
@@ -667,4 +663,16 @@ class WCS_PayPal_Reference_Transaction_API_Request {
private function round( $number, $precision = 2 ) {
return round( (float) $number, $precision );
}
+
+ /**
+ * Format prices.
+ *
+ * @since 2.2.12
+ * @param float|int $price
+ * @param int $decimals Optional. The number of decimal points.
+ * @return string
+ */
+ private function price_format( $price, $decimals = 2 ) {
+ return number_format( $price, $decimals, '.', '' );
+ }
}
diff --git a/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php b/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php
index b3f209b..bdf956e 100644
--- a/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php
+++ b/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php
@@ -245,7 +245,7 @@ class WCS_PayPal_Standard_Switcher {
_deprecated_function( __METHOD__, '2.1', __CLASS__ . 'cancel_paypal_standard_after_switch( $order )' );
$order = wc_get_order( $order_id );
- $order_completed = in_array( $new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
+ $order_completed = in_array( $new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id, $order ), 'processing', 'completed' ) ) && in_array( $old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
if ( $order_completed && wcs_order_contains_switch( $order_id ) ) {
self::cancel_paypal_standard_after_switch( $order );
diff --git a/includes/wcs-cart-functions.php b/includes/wcs-cart-functions.php
index 545dacf..1d4b01c 100644
--- a/includes/wcs-cart-functions.php
+++ b/includes/wcs-cart-functions.php
@@ -175,7 +175,7 @@ function wcs_cart_totals_shipping_method_price_label( $method, $cart ) {
$price_label = '';
- if ( $method->cost > 0 ) {
+ if ( $method->cost != 0 ) {
if ( WC()->cart->tax_display_cart == 'excl' ) {
$price_label .= wcs_cart_price_string( $method->cost, $cart );
@@ -207,11 +207,14 @@ function wcs_cart_totals_taxes_total_html( $cart ) {
}
/**
- * Display a recurring coupon's value
+ * Display a recurring coupon's value.
+ *
+ * @see wc_cart_totals_coupon_html()
*
* @access public
- * @param string $coupon
- * @return void
+ *
+ * @param string|WC_Coupon $coupon
+ * @param WC_Cart $cart
*/
function wcs_cart_totals_coupon_html( $coupon, $cart ) {
if ( is_string( $coupon ) ) {
@@ -235,10 +238,11 @@ function wcs_cart_totals_coupon_html( $coupon, $cart ) {
// get rid of empty array elements
$value = implode( ', ', array_filter( $value ) );
- // Apply WooCommerce core filter
- $value = apply_filters( 'woocommerce_cart_totals_coupon_html', $value, $coupon );
+ // Apply filters.
+ $html = apply_filters( 'wcs_cart_totals_coupon_html', $value, $coupon, $cart );
+ $html = apply_filters( 'woocommerce_cart_totals_coupon_html', $html, $coupon, $discount_html );
- echo wp_kses_post( apply_filters( 'wcs_cart_totals_coupon_html', wcs_cart_price_string( $value, $cart ), $coupon, $cart ) );
+ echo wp_kses( $html, array_replace_recursive( wp_kses_allowed_html( 'post' ), array( 'a' => array( 'data-coupon' => true ) ) ) );
}
/**
@@ -278,7 +282,7 @@ function wcs_cart_totals_order_total_html( $cart ) {
* Return a formatted price string for a given cart object
*
* @access public
- * @return void
+ * @return string
*/
function wcs_cart_price_string( $recurring_amount, $cart ) {
diff --git a/includes/wcs-formatting-functions.php b/includes/wcs-formatting-functions.php
index 324500f..ecf3bd6 100644
--- a/includes/wcs-formatting-functions.php
+++ b/includes/wcs-formatting-functions.php
@@ -236,3 +236,27 @@ function wcs_get_human_time_diff( $timestamp_gmt ) {
return $date_to_display;
}
+
+/**
+ * Works around the wp_kses() limitation of not accepting attribute names with underscores.
+ *
+ * @param string $content Content to filter through kses.
+ * @param array $allowed_html List of allowed HTML elements.
+ * @return string Filtered string of HTML.
+ * @since 2.2.17
+ */
+function wp_kses_allow_underscores( $content, $allowed_html ) {
+
+ /* Replace the underscore _ with a double hyphen -- in the attribute names */
+ foreach ( $allowed_html as $tag => &$attributes ) {
+ $attribute_names = array_keys( $attributes );
+ $attribute_values = array_values( $attributes );
+ $attributes = array_combine( preg_replace( '/_/', '--', $attribute_names ), $attribute_values );
+ }
+
+ /* Replace the underscore _ with a double hyphen -- in the content as well.
+ The assumption is that such an attribute name would be followed by a = */
+ $content = preg_replace( '/\b([-A-Za-z]+)_([-A-Za-z]+)=/', '$1--$2=', $content );
+ $content = wp_kses( $content, $allowed_html ); // Now pass through wp_kses the attribute name with --
+ return preg_replace( '/\b([-A-Za-z]+)--([-A-Za-z]+)=/', '$1_$2=', $content ); // Replace the _ back
+}
diff --git a/includes/wcs-order-functions.php b/includes/wcs-order-functions.php
index 92f734a..a5c5cca 100644
--- a/includes/wcs-order-functions.php
+++ b/includes/wcs-order-functions.php
@@ -607,7 +607,7 @@ function wcs_get_order_item_name( $order_item, $include = array() ) {
foreach ( $order_item['item_meta'] as $meta_key => $meta_value ) {
- $meta_value = $meta_value[0];
+ $meta_value = WC_Subscriptions::is_woocommerce_pre( 3.0 ) ? $meta_value[0] : $meta_value;
// Skip hidden core fields
if ( in_array( $meta_key, apply_filters( 'woocommerce_hidden_order_itemmeta', array(
@@ -624,8 +624,8 @@ function wcs_get_order_item_name( $order_item, $include = array() ) {
continue;
}
- // Skip serialised meta
- if ( is_serialized( $meta_value ) ) {
+ // Skip serialised or array meta values
+ if ( is_serialized( $meta_value ) || is_array( $meta_value ) ) {
continue;
}
@@ -660,7 +660,7 @@ function wcs_get_line_item_name( $line_item ) {
foreach ( $line_item['item_meta'] as $meta_key => $meta_value ) {
- $meta_value = $meta_value[0];
+ $meta_value = WC_Subscriptions::is_woocommerce_pre( 3.0 ) ? $meta_value[0] : $meta_value;
// Skip hidden core fields
if ( in_array( $meta_key, apply_filters( 'woocommerce_hidden_order_itemmeta', array(
@@ -677,8 +677,8 @@ function wcs_get_line_item_name( $line_item ) {
continue;
}
- // Skip serialised meta
- if ( is_serialized( $meta_value ) ) {
+ // Skip serialised or array meta values
+ if ( is_serialized( $meta_value ) || is_array( $meta_value ) ) {
continue;
}
diff --git a/includes/wcs-time-functions.php b/includes/wcs-time-functions.php
index 5042e91..b24c44f 100644
--- a/includes/wcs-time-functions.php
+++ b/includes/wcs-time-functions.php
@@ -34,7 +34,8 @@ function wcs_get_subscription_period_strings( $number = 1, $period = '' ) {
'month' => sprintf( _nx( 'month', '%s months', $number, 'Subscription billing period.', 'woocommerce-subscriptions' ), $number ),
// translators: placeholder is number of years. (e.g. "Bill this every year / 4 years")
'year' => sprintf( _nx( 'year', '%s years', $number, 'Subscription billing period.', 'woocommerce-subscriptions' ), $number ),
- )
+ ),
+ $number
);
return ( ! empty( $period ) ) ? $translated_periods[ $period ] : $translated_periods;
@@ -55,7 +56,8 @@ function wcs_get_subscription_trial_period_strings( $number = 1, $period = '' )
'week' => sprintf( _n( '%s week', 'a %s-week', $number, 'woocommerce-subscriptions' ), $number ),
'month' => sprintf( _n( '%s month', 'a %s-month', $number, 'woocommerce-subscriptions' ), $number ),
'year' => sprintf( _n( '%s year', 'a %s-year', $number, 'woocommerce-subscriptions' ), $number ),
- )
+ ),
+ $number
);
return ( ! empty( $period ) ) ? $translated_periods[ $period ] : $translated_periods;
@@ -123,7 +125,7 @@ function wcs_get_subscription_ranges( $subscription_period = '' ) {
$subscription_period = '';
}
- $locale = get_locale();
+ $locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
$subscription_ranges = WC_Subscriptions::$cache->cache_and_get( 'wcs-sub-ranges-' . $locale, 'wcs_get_non_cached_subscription_ranges', array(), 3 * HOUR_IN_SECONDS );
diff --git a/includes/wcs-user-functions.php b/includes/wcs-user-functions.php
index 7b6fe32..0249f99 100644
--- a/includes/wcs-user-functions.php
+++ b/includes/wcs-user-functions.php
@@ -123,10 +123,12 @@ function wcs_get_new_user_role_names( $role_new ) {
/**
* Check if a user has a subscription, optionally to a specific product and/or with a certain status.
*
- * @param int (optional) The ID of a user in the store. If left empty, the current user's ID will be used.
- * @param int (optional) The ID of a product in the store. If left empty, the function will see if the user has any subscription.
- * @param mixed (optional) A valid subscription status string or array. If left empty, the function will see if the user has a subscription of any status.
+ * @param int $user_id (optional) The ID of a user in the store. If left empty, the current user's ID will be used.
+ * @param int $product_id (optional) The ID of a product in the store. If left empty, the function will see if the user has any subscription.
+ * @param mixed $status (optional) A valid subscription status string or array. If left empty, the function will see if the user has a subscription of any status.
* @since 2.0
+ *
+ * @return bool
*/
function wcs_user_has_subscription( $user_id = 0, $product_id = '', $status = 'any' ) {
@@ -164,9 +166,10 @@ function wcs_user_has_subscription( $user_id = 0, $product_id = '', $status = 'a
*
* @param int $user_id (optional) The id of the user whose subscriptions you want. Defaults to the currently logged in user.
* @since 2.0
+ *
+ * @return WC_Subscription[]
*/
function wcs_get_users_subscriptions( $user_id = 0 ) {
-
if ( 0 === $user_id || empty( $user_id ) ) {
$user_id = get_current_user_id();
}
@@ -174,18 +177,7 @@ function wcs_get_users_subscriptions( $user_id = 0 ) {
$subscriptions = apply_filters( 'wcs_pre_get_users_subscriptions', array(), $user_id );
if ( empty( $subscriptions ) && 0 !== $user_id && ! empty( $user_id ) ) {
-
- $post_ids = get_posts( array(
- 'posts_per_page' => -1,
- 'post_status' => 'any',
- 'post_type' => 'shop_subscription',
- 'orderby' => 'date',
- 'order' => 'desc',
- 'meta_key' => '_customer_user',
- 'meta_value' => $user_id,
- 'meta_compare' => '=',
- 'fields' => 'ids',
- ) );
+ $post_ids = wcs_get_cached_user_subscription_ids( $user_id );
foreach ( $post_ids as $post_id ) {
$subscription = wcs_get_subscription( $post_id );
@@ -199,6 +191,65 @@ function wcs_get_users_subscriptions( $user_id = 0 ) {
return apply_filters( 'wcs_get_users_subscriptions', $subscriptions, $user_id );
}
+/**
+ * Get subscription IDs for the given user.
+ *
+ * @author Jeremy Pry
+ *
+ * @param int $user_id The ID of the user whose subscriptions you want.
+ *
+ * @return array Array of Subscription IDs.
+ */
+function wcs_get_users_subscription_ids( $user_id ) {
+ $query = new WP_Query();
+
+ return $query->query( array(
+ 'post_type' => 'shop_subscription',
+ 'posts_per_page' => -1,
+ 'post_status' => 'any',
+ 'orderby' => 'date',
+ 'order' => 'desc',
+ 'fields' => 'ids',
+ 'no_found_rows' => true,
+ 'ignore_sticky_posts' => true,
+ 'meta_query' => array(
+ array(
+ 'key' => '_customer_user',
+ 'value' => $user_id,
+ ),
+ ),
+ ) );
+}
+
+/**
+ * Get subscription IDs for a user using caching.
+ *
+ * @author Jeremy Pry
+ *
+ * @param int $user_id The ID of the user whose subscriptions you want.
+ *
+ * @return array Array of subscription IDs.
+ */
+function wcs_get_cached_user_subscription_ids( $user_id = 0 ) {
+ $user_id = absint( $user_id );
+ if ( 0 === $user_id ) {
+ $user_id = get_current_user_id();
+ }
+
+ // If the user ID is still zero, bail early.
+ if ( 0 === $user_id ) {
+ return apply_filters( 'wcs_get_cached_users_subscription_ids', array(), $user_id );
+ }
+
+ $subscription_ids = WC_Subscriptions::$cache->cache_and_get(
+ "wcs_user_subscriptions_{$user_id}",
+ 'wcs_get_users_subscription_ids',
+ array( $user_id )
+ );
+
+ return apply_filters( 'wcs_get_cached_users_subscription_ids', $subscription_ids, $user_id );
+}
+
/**
* Return a link for subscribers to change the status of their subscription, as specified with $status parameter
*
@@ -320,7 +371,7 @@ function wcs_get_all_user_actions_for_subscription( $subscription, $user_id ) {
* @param array $allcaps
* @param array $caps
* @param array $args
- * @return bool
+ * @return array
*/
function wcs_user_has_capability( $allcaps, $caps, $args ) {
if ( isset( $caps[0] ) ) {
diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot
index a65f7a8..1d9980a 100644
--- a/languages/woocommerce-subscriptions.pot
+++ b/languages/woocommerce-subscriptions.pot
@@ -1,15 +1,15 @@
-# Copyright (C) 2017 Prospress Inc.
+# Copyright (C) 2018 Prospress Inc.
# This file is distributed under the same license as the WooCommerce Subscriptions package.
msgid ""
msgstr ""
-"Project-Id-Version: WooCommerce Subscriptions 2.2.15\n"
+"Project-Id-Version: WooCommerce Subscriptions 2.2.17\n"
"Report-Msgid-Bugs-To: "
"https://github.com/Prospress/woocommerce-subscriptions/issues\n"
-"POT-Creation-Date: 2017-11-07 04:27:49+00:00\n"
+"POT-Creation-Date: 2018-01-29 04:39:30+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