Skip to content
Snippets Groups Projects
Commit 5394d6b8 authored by Rich Lott / Artful Robot's avatar Rich Lott / Artful Robot
Browse files

artfulrobot-moat-6.11.2 Replace Firewall with Moat extension

fix basetest
parent e08acace
No related branches found
No related tags found
No related merge requests found
......@@ -531,6 +531,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
$jsVars['csrfToken'] = $firewall->generateCSRFToken($context);
}
// Generate csrf token valid for 5 mins.
$event = Civi\Core\Event\GenericHookEvent::create([
'createdToken' => NULL,
'validTo' => 5,
'data' => $context ?? NULL,
]);
Civi::dispatcher()->dispatch('civi.moat.token', $event);
$jsVars['csrfToken'] = $event->createdToken;
// Add CSS via region (it won't load on drupal webform if added via \Civi::resources()->addStyleFile)
CRM_Core_Region::instance('billing-block')->add([
'styleUrl' => \Civi::service('asset_builder')->getUrl(
......
......@@ -26,7 +26,7 @@ class CRM_Stripe_Check {
* @var string
*/
const MIN_VERSION_MJWSHARED = '1.3';
const MIN_VERSION_FIREWALL = '1.5.9';
const MIN_VERSION_MOAT = '1.1';
/**
* @var array
......@@ -48,7 +48,7 @@ class CRM_Stripe_Check {
*/
public function checkRequirements() {
$this->checkExtensionMjwshared();
$this->checkExtensionFirewall();
$this->checkExtensionMoat();
$this->checkWebhooks();
$this->checkFailedPaymentIntents();
return $this->messages;
......@@ -147,8 +147,8 @@ class CRM_Stripe_Check {
/**
* @throws \CRM_Core_Exception
*/
private function checkExtensionFirewall() {
$extensionName = 'firewall';
private function checkExtensionMoat() {
$extensionName = 'moat';
$extensions = civicrm_api3('Extension', 'get', [
'full_name' => $extensionName,
......@@ -157,9 +157,9 @@ class CRM_Stripe_Check {
if (empty($extensions['count']) || ($extensions['values'][$extensions['id']]['status'] !== 'installed')) {
$message = new CRM_Utils_Check_Message(
__FUNCTION__ . 'stripe_recommended',
E::ts('If you are using Stripe to accept payments on public forms (eg. contribution/event registration forms) it is required that you install the <strong><a href="https://lab.civicrm.org/extensions/firewall">firewall</a></strong> extension.
E::ts('If you are using Stripe to accept payments on public forms (eg. contribution/event registration forms) it is required that you install the <strong><a href="https://lab.civicrm.org/extensions/moat">moat</a></strong> extension.
Some sites have become targets for spammers who use the payment endpoint to try and test credit cards by submitting invalid payments to your Stripe account.'),
E::ts('Required Extension: firewall'),
E::ts('Required Extension: Moat'),
\Psr\Log\LogLevel::ERROR,
'fa-lightbulb-o'
);
......@@ -172,7 +172,7 @@ class CRM_Stripe_Check {
$this->messages[] = $message;
}
if (isset($extensions['id']) && $extensions['values'][$extensions['id']]['status'] === 'installed') {
$this->requireExtensionMinVersion($extensionName, CRM_Stripe_Check::MIN_VERSION_FIREWALL, $extensions['values'][$extensions['id']]['version']);
$this->requireExtensionMinVersion($extensionName, CRM_Stripe_Check::MIN_VERSION_MOAT, $extensions['values'][$extensions['id']]['version']);
}
}
......
......@@ -391,24 +391,21 @@ class CRM_Stripe_PaymentIntent {
*/
$resultObject = (object) ['ok' => FALSE, 'message' => '', 'data' => []];
if (class_exists('\Civi\Firewall\Event\FraudEvent')) {
if (!empty($this->extraData)) {
// The firewall will block IP addresses when it detects fraud.
// This additionally checks if the same details are being used on a different IP address.
$ipAddress = \Civi\Firewall\Firewall::getIPAddress();
// Where a payment is declined as likely fraud, log it as a more serious exception
$numberOfFailedAttempts = \Civi\Api4\StripePaymentintent::get(FALSE)
->selectRowCount()
->addWhere('extra_data', '=', $this->extraData)
->addWhere('status', '=', 'failed')
->addWhere('created_date', '>', '-2 hours')
->execute()
->count();
if ($numberOfFailedAttempts > 5) {
\Civi\Firewall\Event\FraudEvent::trigger($ipAddress, CRM_Utils_String::ellipsify('StripeProcessPaymentIntent: ' . $this->extraData, 255));
}
}
// Control floods of intent processing
$event = Civi\Core\Event\GenericHookEvent::create([
'action' => 'stripe_process_payment_intent',
'identifiers' => ['ip'],
'ok' => TRUE,
]);
if (!empty($this->extraData)) {
// Enable us to ensure (in moat config) that same intent is not tried twice.
$event->identifiers[] = "stripe_intent_id:$this->extraData";
}
Civi::dispatcher()->dispatch('civi.moat.drip', $event);
if (!$event->ok) {
// A flood was triggered, but the moat config didn't cause a die
// (it probably should have), so exit now gracefully.
return $resultObject;
}
$intentParams = [];
......@@ -472,46 +469,30 @@ class CRM_Stripe_PaymentIntent {
if ($e instanceof \Stripe\Exception\CardException) {
// Determine likely fraud
$fraud = FALSE;
if (method_exists('\Civi\Firewall\Firewall', 'getIPAddress')) {
$ipAddress = \Civi\Firewall\Firewall::getIPAddress();
if ($e->getDeclineCode() === 'fraudulent') {
$fraud = TRUE;
}
else {
$ipAddress = \CRM_Utils_System::ipAddress();
}
// Where a payment is declined as likely fraud, log it as a more serious exception
if (class_exists('\Civi\Firewall\Event\FraudEvent')) {
// Fraud response from issuer
if ($e->getDeclineCode() === 'fraudulent') {
$fraud = TRUE;
}
// Look for fraud detected by Stripe Radar
else {
$jsonBody = $e->getJsonBody();
if (!empty($jsonBody['error']['payment_intent']['charges']['data'])) {
foreach ($jsonBody['error']['payment_intent']['charges']['data'] as $charge) {
if ($charge['outcome']['type'] === 'blocked') {
$fraud = TRUE;
break;
}
$jsonBody = $e->getJsonBody();
if (!empty($jsonBody['error']['payment_intent']['charges']['data'])) {
foreach ($jsonBody['error']['payment_intent']['charges']['data'] as $charge) {
if ($charge['outcome']['type'] === 'blocked') {
$fraud = TRUE;
break;
}
}
}
if ($fraud) {
\Civi\Firewall\Event\FraudEvent::trigger($ipAddress, 'CRM_Stripe_PaymentIntent::processPaymentIntent');
}
}
// Multiple declined card attempts is an indicator of card testing
if (!$fraud && class_exists('\Civi\Firewall\Event\DeclinedCardEvent')) {
\Civi\Firewall\Event\DeclinedCardEvent::trigger($ipAddress, 'CRM_Stripe_PaymentIntent::processPaymentIntent');
}
// Allow configurable numbers of decline and fraud.
$event = Civi\Core\Event\GenericHookEvent::create([
'action' => $fraud ? 'stripe_likely_fraud' : 'stripe_decline',
'identifiers' => ['ip'],
'ok' => TRUE,
]);
Civi::dispatcher()->dispatch('civi.moat.drip', $event);
// Returned message should not indicate whether fraud was detected
$message = $parsedError['message'];
......
......@@ -9,7 +9,6 @@
+--------------------------------------------------------------------+
*/
use Civi\Firewall\Firewall;
use CRM_Stripe_ExtensionUtil as E;
/**
......
......@@ -26,7 +26,7 @@ There are a number of planned features that depend on funding/resources. See [Ro
#### Recommended extensions
* [Firewall extension](https://civicrm.org/extensions/firewall).
* [Moat extension](https://codeberg.org/artfulrobot/moat).
* [contributiontransactlegacy extension](https://civicrm.org/extensions/contribution-transact-api) if using drupal 7 webform.
**Please ensure that you are running the "Stripe: Cleanup" scheduled job every hour or you will have issues with failed/uncaptured payments appearing on customer credit cards and blocking their balance for up to a week!**
......
# Moat configuration
Moat is a dynamic firewall-like API. When implemented by extensions (e.g. this
one), it allows site admins to say how much of what should be allowed to occur.
This extension suggests the following configuration for a 'normal' alert level.
## Event: `stripe_process_payment_intent`
What's too many attempts to process a payment intent per IP? Suggest: 3/hour and 9/week, action: die, block
What's too many attempts to process a particular payment intent? Suggest: 1/year, action: die
## Event: `stripe_likely_fraud`
How much likely fraud is too much per IP? 1/week and 2/month, action: die, block
## Event: `stripe_decline`
How many declines per IP? Declines are normal, but too many indicates card testing. How about 6/hour and 10/month, action: die, block
## Higher alert levels
Moat can be configured to move up to a heightened alert level in certain situations. You may want to provide tighter rules for these situations.
......@@ -9,6 +9,10 @@ Releases use the following numbering system:
* **[BC]**: Items marked with [BC] indicate a breaking change that will require updates to your code if you are using that code in your extension.
## artfulrobot-moat fork
* Removes Firewall, implements Moat.
## Release 6.11.2 (2024-08-12)
* Set next_sched_contribution_date to the actual date retrieved from Stripe instead of using CiviCRM calculated value.
......@@ -31,7 +35,7 @@ Releases use the following numbering system:
* CiviCRM extension [mjwshared](https://lab.civicrm.org/extensions/mjwshared)@1.3.
If you previously used the API3 Stripe.importX API calls they have been removed and replaced with a separate extension:
If you previously used the API3 Stripe.importX API calls they have been removed and replaced with a separate extension:
* CiviCRM extension [stripeimport](https://lab.civicrm.org/extensions/stripeimport)
### New Features
......@@ -119,7 +123,6 @@ Update tests to use StripePaymentintent.ProcessPublic instead of (deleted) API3
* Support (and require) API version 2023-08-16.
* Update stripe-php library from 9 to 12.
## Release 6.9.4 (2023-09-22)
**Stripe API version 2023-08-16 is NOT supported.
The last supported API version for 6.9.x is 2022-11-15 See [#446](https://lab.civicrm.org/extensions/stripe/-/issues/446)**
......
<?xml version="1.0"?>
<extension key="com.drastikbydesign.stripe" type="module">
<file>stripe</file>
<name>Stripe Payment Processor</name>
<name>Stripe Payment Processor (Moat fork)</name>
<description>Accept payments using https://stripe.com/</description>
<urls>
<url desc="Main Extension Page">https://lab.civicrm.org/extensions/stripe</url>
......@@ -21,13 +21,15 @@
<compatibility>
<ver>5.74</ver>
</compatibility>
<comments>Original Author: Joshua Walker (drastik) - Drastik by Design.
<comments>
This is the Moat fork by Rich Lott / Artful Robot. Original Author: Joshua
Walker (drastik) - Drastik by Design.
Jamie Mcclelland (ProgressiveTech) did a lot of the 5.x compatibility work.
Stripe generously donates a portion of each transaction to support CiviCRM.
</comments>
<requires>
<ext>mjwshared</ext>
<ext>firewall</ext>
<ext>moat</ext>
</requires>
<civix>
<namespace>CRM/Stripe</namespace>
......
......@@ -22,6 +22,13 @@ use CRM_Stripe_ExtensionUtil as E;
*/
function stripe_civicrm_config(&$config) {
_stripe_civix_civicrm_config($config);
if (!isset(Civi::$statics[__FUNCTION__])) {
Civi::$statics[__FUNCTION__] = TRUE;
// Register hook/event listeners here
$d = Civi::dispatcher();
$d->addListener('civi.moat.getimplementations', 'stripe_handleGetMoatImplementations');
}
}
/**
......@@ -168,3 +175,50 @@ function stripe_civicrm_shutdown_updatestripecustomer(int $contactID) {
}
}
function stripe_handleGetMoatImplementations(\Civi\Core\Event\GenericHookEvent $event) {
$event->actions['stripe'] = [
'stripe_process_payment_intent' => [
'description' => 'This fires when a payment intent is being processed. up to 3/ip/hour, up to 10/ip/week is probably ok. But each intent must only be processed once.',
'suggestedConfig' => [
'ip4:' => [[
'rates' => [
['limit' => 3, 'minutes' => 60],
['limit' => 10, 'minutes' => 60*24*7]
],
'flood' => [ 'block' => TRUE, 'die' => 400 ]
]],
'stripe_intent_id:' => [[
'rates' => [['limit' => 2, 'minutes' => 60*24*7]],
'flood' => [ 'block' => TRUE, 'die' => 400 ]
]],
]
],
'stripe_likely_fraud' => [
'description' => 'A payment was declined and it looks likely to have been for fraud reasons. Allow 1 max per day per ip, 2 max per ip per week.',
'suggestedConfig' => [
'ip4:' => [[
'rates' => [['limit' => 2, 'minutes' => 60*24], ['limit' => 3, 'minutes' => 60*24*7]],
'flood' => [ 'block' => TRUE, 'die' => 400 ]
]],
]
],
'stripe_decline' => [
'description' => 'A payment was declined on some basis, likely non-fraudulent. Limits of 10/day, 20/week.',
'suggestedConfig' => [
'ip4:' => [[
'rates' => [['limit' => 10, 'minutes' => 60*24], ['limit' => 20, 'minutes' => 60*24*7]],
'flood' => [ 'block' => TRUE, 'die' => 400 ]
]],
]
],
];
$event->identifierPrefixes['stripe'] = [
'stripe_intent_id:' => [
'description' => 'A particular payment intent',
]
];
}
......@@ -61,7 +61,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
*/
protected array $requiredExtensions = [
'mjwshared' => 'mjwshared',
'firewall' => 'firewall',
'moat' => 'moat',
];
/**
......@@ -82,7 +82,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
$reInstall = FALSE;
if (!isset($reInstallOnce)) {
$reInstallOnce=TRUE;
$reInstallOnce = TRUE;
$reInstall = TRUE;
}
......@@ -136,7 +136,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
'last_name' => 'Lopez'
]);;
$this->contactID = $results['id'];
$this->contact = (Object) array_pop($results['values']);
$this->contact = (object) array_pop($results['values']);
// Now we have to add an email address.
$email = 'susie@example.org';
......@@ -240,7 +240,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
$mockPaymentMethod = $this->createMock('Stripe\\PaymentMethod');
$mockPaymentMethod->method('__get')
->will($this->returnValueMap([
[ 'id', 'pm_mock']
['id', 'pm_mock']
]));
$stripeClient->paymentMethods = $this->createMock('Stripe\\Service\\PaymentMethodService');
$stripeClient->paymentMethods
......@@ -330,7 +330,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
]));
$mockRefund = new PropertySpy('Refund', [
'amount_refunded' => $this->total*100,
'amount_refunded' => $this->total * 100,
'charge_id' => 'ch_mock', //xxx
'created' => time(),
'currency' => 'usd',
......@@ -340,7 +340,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
$stripeClient->refunds = $this->createMock('Stripe\\Service\\RefundService');
$stripeClient->refunds
->method('all')
->willReturn(new PropertySpy('refunds.all', [ 'data' => [ $mockRefund ] ]));
->willReturn(new PropertySpy('refunds.all', ['data' => [$mockRefund]]));
}
/**
......@@ -415,7 +415,6 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
$paymentIntentID = NULL;
$paymentMethodID = NULL;
$firewall = new \Civi\Firewall\Firewall();
if (!isset($params['is_recur'])) {
// Send in payment method to get payment intent.
$paymentIntentParams = [
......@@ -424,7 +423,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
'payment_processor_id' => $this->paymentProcessorID,
'payment_intent_id' => $params['paymentIntentID'] ?? NULL,
'description' => NULL,
'csrfToken' => $firewall->generateCSRFToken(),
'csrfToken' => $this->generateCSRFToken(),
];
$result = \Civi\Api4\StripePaymentintent::processPublic(TRUE)
->setPaymentMethodID($paymentMethod->id)
......@@ -432,7 +431,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
->setPaymentProcessorID($this->paymentProcessorID)
->setIntentID($params['paymentIntentID'] ?? NULL)
->setDescription(NULL)
->setCsrfToken($firewall->generateCSRFToken())
->setCsrfToken($this->generateCSRFToken())
->execute();
// $result = civicrm_api3('StripePaymentintent', 'process', $paymentIntentParams);
......@@ -440,8 +439,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
throw new CRM_Core_Exception('StripePaymentintent::processPublic did not return success');
}
$paymentIntentID = $result['paymentIntent']['id'];
}
else {
} else {
$paymentMethodID = $paymentMethod->id;
}
......@@ -482,7 +480,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
if (array_key_exists('contributionRecurID', $ret)) {
// Get processor id.
$sql = "SELECT processor_id FROM civicrm_contribution_recur WHERE id = %0";
$params = [ 0 => [ $ret['contributionRecurID'], 'Integer' ] ];
$params = [0 => [$ret['contributionRecurID'], 'Integer']];
$dao = CRM_Core_DAO::executeQuery($sql, $params);
if ($dao->N > 0) {
$dao->fetch();
......@@ -492,6 +490,23 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
return $ret;
}
protected function generateCSRFToken(): ?string {
// Use Moat to generate token, if we have it.
$event = Civi\Core\Event\GenericHookEvent::create(['createdToken' => '']);
Civi::dispatcher()->dispatch('civi.moat.token', $event);
if ($event->createdToken) {
return $event->createdToken;
}
// Use Firewall if we have it.
if (class_exists('\\Civi\Firewall\Firewall')) {
return (new \Civi\Firewall\Firewall())->generateCSRFToken();
}
return null;
}
/**
* Confirm that transaction id is legit and went through.
*
......@@ -504,8 +519,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
try {
$processor->stripeClient->charges->retrieve($this->trxn_id);
$found = TRUE;
}
catch (Exception $e) {
} catch (Exception $e) {
$found = FALSE;
}
......@@ -521,7 +535,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
* @throws \CRM_Core_Exception
*/
public function setupPendingContribution($params = []): array {
$contribution = civicrm_api3('contribution', 'create', array_merge([
$contribution = civicrm_api3('contribution', 'create', array_merge([
'contact_id' => $this->contactID,
'payment_processor_id' => $this->paymentProcessorID,
// processor provided ID - use contact ID as proxy.
......@@ -530,7 +544,7 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
'financial_type_id' => $this->financialTypeID,
'contribution_status_id' => 'Pending',
'is_test' => 1,
], $params));
], $params));
$this->assertEquals(0, $contribution['is_error']);
$contribution = \Civi\Api4\Contribution::get(FALSE)
->addWhere('id', '=', $contribution['id'])
......@@ -552,7 +566,10 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
protected function checkContrib(array $expectations, $contribution = NULL) {
if (!empty($expectations['contribution_status_id'])) {
$expectations['contribution_status_id'] = CRM_Core_PseudoConstant::getKey(
'CRM_Contribute_BAO_Contribution', 'contribution_status_id', $expectations['contribution_status_id']);
'CRM_Contribute_BAO_Contribution',
'contribution_status_id',
$expectations['contribution_status_id']
);
}
if (!is_array($contribution)) {
......@@ -602,7 +619,10 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
protected function checkContribRecur(array $expectations) {
if (!empty($expectations['contribution_status_id'])) {
$expectations['contribution_status_id'] = CRM_Core_PseudoConstant::getKey(
'CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $expectations['contribution_status_id']);
'CRM_Contribute_BAO_ContributionRecur',
'contribution_status_id',
$expectations['contribution_status_id']
);
}
$this->assertGreaterThan(0, $this->contributionRecurID);
$contributionRecur = \Civi\Api4\ContributionRecur::get(FALSE)
......@@ -626,7 +646,10 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
protected function checkPayment(array $expectations, $contributionID = NULL) {
if (!empty($expectations['contribution_status_id'])) {
$expectations['contribution_status_id'] = CRM_Core_PseudoConstant::getKey(
'CRM_Contribute_BAO_Contribution', 'contribution_status_id', $expectations['contribution_status_id']);
'CRM_Contribute_BAO_Contribution',
'contribution_status_id',
$expectations['contribution_status_id']
);
}
$contributionID = $contributionID ?? $this->contributionID;
......@@ -640,7 +663,6 @@ abstract class CRM_Stripe_BaseTest extends CiviUnitTestCase implements HeadlessI
$this->assertEquals($expect, $payment[$field], "Expected Payment.$field = " . json_encode($expect));
}
}
}
/**
......@@ -680,7 +702,7 @@ class PropertySpy implements ArrayAccess, Iterator, Countable, JsonSerializable
public static $globalLog = [];
public static $globalObjects = 0;
protected $iteratorIdx=0;
protected $iteratorIdx = 0;
// Iterator
public function current() {
// $this->warning("Iterating " . array_keys($this->_props)[$this->key()]);
......@@ -734,8 +756,9 @@ class PropertySpy implements ArrayAccess, Iterator, Countable, JsonSerializable
if (is_array($data)) {
return new static($name, $data);
}
throw new \Exception("PropertySpy::fromMixed requires array|PropertySpy, got "
. is_object($data) ? get_class($data) : gettype($data)
throw new \Exception(
"PropertySpy::fromMixed requires array|PropertySpy, got "
. is_object($data) ? get_class($data) : gettype($data)
);
}
......@@ -746,24 +769,19 @@ class PropertySpy implements ArrayAccess, Iterator, Countable, JsonSerializable
. json_encode($this->localLog, JSON_PRETTY_PRINT) . "\n";
if (static::$outputMode === 'print') {
print $msg;
}
elseif (static::$outputMode === 'log') {
} elseif (static::$outputMode === 'log') {
\Civi::log()->debug($msg);
}
elseif (static::$outputMode === 'exception') {
} elseif (static::$outputMode === 'exception') {
throw new \RuntimeException($msg);
}
}
elseif (static::$buffer === 'global' && static::$globalObjects === 0) {
} elseif (static::$buffer === 'global' && static::$globalObjects === 0) {
// End of run.
$msg = "PropertySpy:\n" . json_encode(static::$globalLog, JSON_PRETTY_PRINT) . "\n";
if (static::$outputMode === 'print') {
print $msg;
}
elseif (static::$outputMode === 'log') {
} elseif (static::$outputMode === 'log') {
\Civi::log()->debug($msg);
}
elseif (static::$outputMode === 'exception') {
} elseif (static::$outputMode === 'exception') {
throw new \RuntimeException($msg);
}
}
......@@ -774,15 +792,12 @@ class PropertySpy implements ArrayAccess, Iterator, Countable, JsonSerializable
// Immediate output
if (static::$outputMode === 'print') {
print "$this->_name $msg\n";
}
elseif (static::$outputMode === 'log') {
} elseif (static::$outputMode === 'log') {
Civi::log()->debug("$this->_name $msg\n");
}
}
elseif (static::$buffer === 'global') {
} elseif (static::$buffer === 'global') {
static::$globalLog[] = "$this->_name $msg";
}
elseif (static::$buffer === 'local') {
} elseif (static::$buffer === 'local') {
$this->localLog[] = $msg;
}
}
......@@ -846,7 +861,6 @@ class PropertySpy implements ArrayAccess, Iterator, Countable, JsonSerializable
public function jsonSerialize() {
return $this->_props;
}
}
/**
......@@ -887,5 +901,4 @@ class ValueMapOrDie implements \PHPUnit\Framework\MockObject\Stub\Stub {
public function toString(): string {
return 'return value from a map or throw InvalidArgumentException';
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment