Ejegg has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/370091 )

Change subject: Update SmashPig and Omnimail-Silverpop
......................................................................

Update SmashPig and Omnimail-Silverpop

Change-Id: If480186992d97fc1ef5748e919a24c0f16d4e662
---
M composer/installed.json
M symfony/event-dispatcher/ContainerAwareEventDispatcher.php
M symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
M symfony/event-dispatcher/EventDispatcher.php
M symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
M symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
M 
symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
M symfony/event-dispatcher/Tests/EventTest.php
M symfony/event-dispatcher/Tests/GenericEventTest.php
M symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
M symfony/event-dispatcher/composer.json
M symfony/event-dispatcher/phpunit.xml.dist
M symfony/polyfill-mbstring/Mbstring.php
M symfony/polyfill-mbstring/composer.json
M symfony/yaml/Escaper.php
M symfony/yaml/Exception/ParseException.php
M symfony/yaml/Inline.php
M symfony/yaml/Parser.php
M symfony/yaml/Tests/DumperTest.php
M symfony/yaml/Tests/Fixtures/sfMergeKey.yml
M symfony/yaml/Tests/InlineTest.php
M symfony/yaml/Tests/ParseExceptionTest.php
M symfony/yaml/Tests/ParserTest.php
M symfony/yaml/Tests/YamlTest.php
M symfony/yaml/phpunit.xml.dist
M wikimedia/omnimail-silverpop/src/Requests/ExportListRequest.php
M wikimedia/omnimail-silverpop/src/Responses/Contact.php
M wikimedia/smash-pig/Core/Configuration.php
A wikimedia/smash-pig/Core/DataFiles/AuditParser.php
M wikimedia/smash-pig/Core/DataStores/QueueWrapper.php
M wikimedia/smash-pig/Core/Listeners/ListenerBase.php
M wikimedia/smash-pig/PaymentProviders/Adyen/Audit/AdyenAudit.php
M wikimedia/smash-pig/PaymentProviders/Adyen/ReferenceData.php
R wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AmazonAudit.php
M wikimedia/smash-pig/PaymentProviders/Amazon/Audit/RefundReport.php
M wikimedia/smash-pig/PaymentProviders/Amazon/Audit/SettlementReport.php
M wikimedia/smash-pig/PaymentProviders/Amazon/Tests/phpunit/AuditTest.php
M wikimedia/smash-pig/PaymentProviders/AstroPay/Audit/AstroPayAudit.php
A wikimedia/smash-pig/PaymentProviders/Ingenico/Audit/IngenicoAudit.php
M wikimedia/smash-pig/PaymentProviders/Ingenico/HostedCheckoutProvider.php
M wikimedia/smash-pig/PaymentProviders/Ingenico/IngenicoPaymentProvider.php
A wikimedia/smash-pig/PaymentProviders/Ingenico/ReferenceData.php
A wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
A wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
A 
wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/hostedPaymentStatus.response
A 
wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/paymentStatus.response
A wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
A wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
M 
wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/HostedCheckoutProviderTest.php
A 
wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/IngenicoPaymentProviderTest.php
M 
wikimedia/smash-pig/PaymentProviders/PayPal/Tests/Data/recurring_payment_profile_created_transformed.json
M wikimedia/smash-pig/config/paypal/main.yaml
52 files changed, 1,158 insertions(+), 527 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/crm/vendor 
refs/changes/91/370091/1

diff --git a/composer/installed.json b/composer/installed.json
index 4a3c9b4..5ba866c 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -560,68 +560,6 @@
         ]
     },
     {
-        "name": "symfony/event-dispatcher",
-        "version": "v2.8.16",
-        "version_normalized": "2.8.16.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/symfony/event-dispatcher.git";,
-            "reference": "74877977f90fb9c3e46378d5764217c55f32df34"
-        },
-        "dist": {
-            "type": "zip",
-            "url": 
"https://api.github.com/repos/symfony/event-dispatcher/zipball/74877977f90fb9c3e46378d5764217c55f32df34";,
-            "reference": "74877977f90fb9c3e46378d5764217c55f32df34",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.9"
-        },
-        "require-dev": {
-            "psr/log": "~1.0",
-            "symfony/config": "~2.0,>=2.0.5|~3.0.0",
-            "symfony/dependency-injection": "~2.6|~3.0.0",
-            "symfony/expression-language": "~2.6|~3.0.0",
-            "symfony/stopwatch": "~2.3|~3.0.0"
-        },
-        "suggest": {
-            "symfony/dependency-injection": "",
-            "symfony/http-kernel": ""
-        },
-        "time": "2017-01-02T20:30:24+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "2.8-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Symfony\\Component\\EventDispatcher\\": ""
-            },
-            "exclude-from-classmap": [
-                "/Tests/"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/";,
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Fabien Potencier",
-                "email": "fab...@symfony.com"
-            },
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors";
-            }
-        ],
-        "description": "Symfony EventDispatcher Component",
-        "homepage": "https://symfony.com";
-    },
-    {
         "name": "symfony/polyfill-php55",
         "version": "v1.3.0",
         "version_normalized": "1.3.0.0",
@@ -674,67 +612,6 @@
         "homepage": "https://symfony.com";,
         "keywords": [
             "compatibility",
-            "polyfill",
-            "portable",
-            "shim"
-        ]
-    },
-    {
-        "name": "symfony/polyfill-mbstring",
-        "version": "v1.3.0",
-        "version_normalized": "1.3.0.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/symfony/polyfill-mbstring.git";,
-            "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
-        },
-        "dist": {
-            "type": "zip",
-            "url": 
"https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4";,
-            "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.3"
-        },
-        "suggest": {
-            "ext-mbstring": "For best performance"
-        },
-        "time": "2016-11-14T01:06:16+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.3-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Symfony\\Polyfill\\Mbstring\\": ""
-            },
-            "files": [
-                "bootstrap.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/";,
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Nicolas Grekas",
-                "email": "p...@tchwork.com"
-            },
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors";
-            }
-        ],
-        "description": "Symfony polyfill for the Mbstring extension",
-        "homepage": "https://symfony.com";,
-        "keywords": [
-            "compatibility",
-            "mbstring",
             "polyfill",
             "portable",
             "shim"
@@ -795,57 +672,6 @@
             }
         ],
         "description": "Symfony HttpFoundation Component",
-        "homepage": "https://symfony.com";
-    },
-    {
-        "name": "symfony/yaml",
-        "version": "v2.8.16",
-        "version_normalized": "2.8.16.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/symfony/yaml.git";,
-            "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2"
-        },
-        "dist": {
-            "type": "zip",
-            "url": 
"https://api.github.com/repos/symfony/yaml/zipball/dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2";,
-            "reference": "dbe61fed9cd4a44c5b1d14e5e7b1a8640cfb2bf2",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.9"
-        },
-        "time": "2017-01-03T13:49:52+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "2.8-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Symfony\\Component\\Yaml\\": ""
-            },
-            "exclude-from-classmap": [
-                "/Tests/"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/";,
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Fabien Potencier",
-                "email": "fab...@symfony.com"
-            },
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors";
-            }
-        ],
-        "description": "Symfony Yaml Component",
         "homepage": "https://symfony.com";
     },
     {
@@ -1887,13 +1713,187 @@
         "description": "Wikimedia Foundation payment processing library"
     },
     {
+        "name": "symfony/event-dispatcher",
+        "version": "v2.8.26",
+        "version_normalized": "2.8.26.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/event-dispatcher.git";,
+            "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d"
+        },
+        "dist": {
+            "type": "zip",
+            "url": 
"https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d";,
+            "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.9"
+        },
+        "require-dev": {
+            "psr/log": "~1.0",
+            "symfony/config": "^2.0.5|~3.0.0",
+            "symfony/dependency-injection": "~2.6|~3.0.0",
+            "symfony/expression-language": "~2.6|~3.0.0",
+            "symfony/stopwatch": "~2.3|~3.0.0"
+        },
+        "suggest": {
+            "symfony/dependency-injection": "",
+            "symfony/http-kernel": ""
+        },
+        "time": "2017-06-02T07:47:27+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "2.8-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\EventDispatcher\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/";,
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fab...@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors";
+            }
+        ],
+        "description": "Symfony EventDispatcher Component",
+        "homepage": "https://symfony.com";
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version": "v1.4.0",
+        "version_normalized": "1.4.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-mbstring.git";,
+            "reference": "f29dca382a6485c3cbe6379f0c61230167681937"
+        },
+        "dist": {
+            "type": "zip",
+            "url": 
"https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937";,
+            "reference": "f29dca382a6485c3cbe6379f0c61230167681937",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "suggest": {
+            "ext-mbstring": "For best performance"
+        },
+        "time": "2017-06-09T14:24:12+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.4-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Mbstring\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/";,
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p...@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors";
+            }
+        ],
+        "description": "Symfony polyfill for the Mbstring extension",
+        "homepage": "https://symfony.com";,
+        "keywords": [
+            "compatibility",
+            "mbstring",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    },
+    {
+        "name": "symfony/yaml",
+        "version": "v2.8.26",
+        "version_normalized": "2.8.26.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/yaml.git";,
+            "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5"
+        },
+        "dist": {
+            "type": "zip",
+            "url": 
"https://api.github.com/repos/symfony/yaml/zipball/4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5";,
+            "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.9"
+        },
+        "time": "2017-06-01T20:52:29+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "2.8-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\Yaml\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/";,
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fab...@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors";
+            }
+        ],
+        "description": "Symfony Yaml Component",
+        "homepage": "https://symfony.com";
+    },
+    {
         "name": "wikimedia/smash-pig",
         "version": "dev-master",
         "version_normalized": "9999999-dev",
         "source": {
             "type": "git",
             "url": 
"https://gerrit.wikimedia.org/r/wikimedia/fundraising/SmashPig.git";,
-            "reference": "28035b9795b5cb70689a142a08377c2d03574956"
+            "reference": "c188e2e90c4a753d17ca8e9ecbd58aa943b5fce2"
         },
         "require": {
             "amzn/login-and-pay-with-amazon-sdk-php": "dev-master",
@@ -1911,7 +1911,7 @@
             "jakub-onderka/php-parallel-lint": "^0.9",
             "phpunit/phpunit": "^4.8"
         },
-        "time": "2017-07-17T14:48:07+00:00",
+        "time": "2017-08-03T18:29:58+00:00",
         "type": "library",
         "installation-source": "source",
         "autoload": {
@@ -1949,73 +1949,13 @@
         ]
     },
     {
-        "name": "symfony/polyfill-php54",
-        "version": "v1.3.0",
-        "version_normalized": "1.3.0.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/symfony/polyfill-php54.git";,
-            "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0"
-        },
-        "dist": {
-            "type": "zip",
-            "url": 
"https://api.github.com/repos/symfony/polyfill-php54/zipball/90e085822963fdcc9d1c5b73deb3d2e5783b16a0";,
-            "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.3"
-        },
-        "time": "2016-11-14T01:06:16+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.3-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Symfony\\Polyfill\\Php54\\": ""
-            },
-            "files": [
-                "bootstrap.php"
-            ],
-            "classmap": [
-                "Resources/stubs"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/";,
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Nicolas Grekas",
-                "email": "p...@tchwork.com"
-            },
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors";
-            }
-        ],
-        "description": "Symfony polyfill backporting some PHP 5.4+ features to 
lower PHP versions",
-        "homepage": "https://symfony.com";,
-        "keywords": [
-            "compatibility",
-            "polyfill",
-            "portable",
-            "shim"
-        ]
-    },
-    {
         "name": "wikimedia/omnimail-silverpop",
         "version": "dev-master",
         "version_normalized": "9999999-dev",
         "source": {
             "type": "git",
             "url": 
"https://github.com/eileenmcnaughton/omnimail-silverpop.git";,
-            "reference": "cd3933b8ee7b263d0472cc44d047b82f3ec4029c"
+            "reference": "c3248b3932e62cd2812aa3502d875b7c12c25c66"
         },
         "require": {
             "league/csv": "^8.0",
@@ -2026,7 +1966,7 @@
         "require-dev": {
             "guzzlehttp/guzzle": "*"
         },
-        "time": "2017-07-09T22:55:21+00:00",
+        "time": "2017-07-27T02:53:46+00:00",
         "type": "library",
         "installation-source": "source",
         "autoload": {
diff --git a/symfony/event-dispatcher/ContainerAwareEventDispatcher.php 
b/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
index 6a02e9f..f2d5a40 100644
--- a/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
+++ b/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
@@ -105,7 +105,7 @@
     public function hasListeners($eventName = null)
     {
         if (null === $eventName) {
-            return (bool) count($this->listenerIds) || (bool) 
count($this->listeners);
+            return $this->listenerIds || $this->listeners || 
parent::hasListeners();
         }
 
         if (isset($this->listenerIds[$eventName])) {
diff --git a/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php 
b/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
index 9b460f5..2f6d9be 100644
--- a/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
+++ b/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -306,6 +306,12 @@
             'event' => $eventName,
             'priority' => $this->getListenerPriority($eventName, $listener),
         );
+
+        // unwrap for correct listener info
+        if ($listener instanceof WrappedListener) {
+            $listener = $listener->getWrappedListener();
+        }
+
         if ($listener instanceof \Closure) {
             $info += array(
                 'type' => 'Closure',
diff --git a/symfony/event-dispatcher/EventDispatcher.php 
b/symfony/event-dispatcher/EventDispatcher.php
index bce44a1..dec7c85 100644
--- a/symfony/event-dispatcher/EventDispatcher.php
+++ b/symfony/event-dispatcher/EventDispatcher.php
@@ -103,7 +103,7 @@
      */
     public function hasListeners($eventName = null)
     {
-        return (bool) count($this->getListeners($eventName));
+        return (bool) $this->getListeners($eventName);
     }
 
     /**
diff --git a/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php 
b/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
index 5e16532..2384916 100644
--- a/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
+++ b/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
@@ -11,11 +11,12 @@
 
 namespace Symfony\Component\EventDispatcher\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
-abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase
+abstract class AbstractEventDispatcherTest extends TestCase
 {
     /* Some pseudo events */
     const preFoo = 'pre.foo';
@@ -55,6 +56,7 @@
     {
         $this->dispatcher->addListener('pre.foo', array($this->listener, 
'preFoo'));
         $this->dispatcher->addListener('post.foo', array($this->listener, 
'postFoo'));
+        $this->assertTrue($this->dispatcher->hasListeners());
         $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
         $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
         $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
diff --git 
a/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php 
b/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
index 46eece7..d99b0bf 100644
--- a/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
+++ b/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
@@ -11,14 +11,16 @@
 
 namespace Symfony\Component\EventDispatcher\Tests\Debug;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\Stopwatch\Stopwatch;
 
-class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
+class TraceableEventDispatcherTest extends TestCase
 {
     public function testAddRemoveListener()
     {
@@ -99,19 +101,39 @@
         $this->assertCount(0, $dispatcher->getListeners('foo'));
     }
 
-    public function testGetCalledListeners()
+    /**
+     * @dataProvider isWrappedDataProvider
+     *
+     * @param bool $isWrapped
+     */
+    public function testGetCalledListeners($isWrapped)
     {
         $dispatcher = new EventDispatcher();
-        $tdispatcher = new TraceableEventDispatcher($dispatcher, new 
Stopwatch());
-        $tdispatcher->addListener('foo', $listener = function () {});
+        $stopWatch = new Stopwatch();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, $stopWatch);
+
+        $listener = function () {};
+        if ($isWrapped) {
+            $listener = new WrappedListener($listener, 'foo', $stopWatch, 
$dispatcher);
+        }
+
+        $tdispatcher->addListener('foo', $listener, 5);
 
         $this->assertEquals(array(), $tdispatcher->getCalledListeners());
-        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 
'type' => 'Closure', 'pretty' => 'closure', 'priority' => 0)), 
$tdispatcher->getNotCalledListeners());
+        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 
'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), 
$tdispatcher->getNotCalledListeners());
 
         $tdispatcher->dispatch('foo');
 
-        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 
'type' => 'Closure', 'pretty' => 'closure', 'priority' => null)), 
$tdispatcher->getCalledListeners());
+        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 
'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), 
$tdispatcher->getCalledListeners());
         $this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
+    }
+
+    public function isWrappedDataProvider()
+    {
+        return array(
+            array(false),
+            array(true),
+        );
     }
 
     public function testGetCalledListenersNested()
@@ -177,14 +199,20 @@
     {
         $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new 
Stopwatch());
         $loop = 1;
+        $dispatchedEvents = 0;
         $dispatcher->addListener('foo', $listener1 = function () use 
($dispatcher, &$loop) {
             ++$loop;
             if (2 == $loop) {
                 $dispatcher->dispatch('foo');
             }
         });
+        $dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
+            ++$dispatchedEvents;
+        });
 
         $dispatcher->dispatch('foo');
+
+        $this->assertSame(2, $dispatchedEvents);
     }
 
     public function testDispatchReusedEventNested()
diff --git 
a/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
 
b/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
index cb04f74..53d7282 100644
--- 
a/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
+++ 
b/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
@@ -11,10 +11,11 @@
 
 namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use 
Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
 
-class RegisterListenersPassTest extends \PHPUnit_Framework_TestCase
+class RegisterListenersPassTest extends TestCase
 {
     /**
      * Tests that event subscribers not implementing EventSubscriberInterface
diff --git a/symfony/event-dispatcher/Tests/EventTest.php 
b/symfony/event-dispatcher/Tests/EventTest.php
index 9a82267..bdc14ab 100644
--- a/symfony/event-dispatcher/Tests/EventTest.php
+++ b/symfony/event-dispatcher/Tests/EventTest.php
@@ -11,13 +11,14 @@
 
 namespace Symfony\Component\EventDispatcher\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 
 /**
  * Test class for Event.
  */
-class EventTest extends \PHPUnit_Framework_TestCase
+class EventTest extends TestCase
 {
     /**
      * @var \Symfony\Component\EventDispatcher\Event
diff --git a/symfony/event-dispatcher/Tests/GenericEventTest.php 
b/symfony/event-dispatcher/Tests/GenericEventTest.php
index aebd82d..c84d3ac 100644
--- a/symfony/event-dispatcher/Tests/GenericEventTest.php
+++ b/symfony/event-dispatcher/Tests/GenericEventTest.php
@@ -11,12 +11,13 @@
 
 namespace Symfony\Component\EventDispatcher\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\GenericEvent;
 
 /**
  * Test class for Event.
  */
-class GenericEventTest extends \PHPUnit_Framework_TestCase
+class GenericEventTest extends TestCase
 {
     /**
      * @var GenericEvent
@@ -95,7 +96,7 @@
         $this->assertEquals('Event', $this->event['name']);
 
         // test getting invalid arg
-        $this->setExpectedException('InvalidArgumentException');
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 
'setExpectedException'}('InvalidArgumentException');
         $this->assertFalse($this->event['nameNotExist']);
     }
 
diff --git a/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php 
b/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
index 0f88680..04f2861 100644
--- a/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
+++ b/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
@@ -11,13 +11,14 @@
 
 namespace Symfony\Component\EventDispatcher\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
 
 /**
  * @author Bernhard Schussek <bschus...@gmail.com>
  */
-class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase
+class ImmutableEventDispatcherTest extends TestCase
 {
     /**
      * @var \PHPUnit_Framework_MockObject_MockObject
diff --git a/symfony/event-dispatcher/composer.json 
b/symfony/event-dispatcher/composer.json
index 282b770..14fc24b 100644
--- a/symfony/event-dispatcher/composer.json
+++ b/symfony/event-dispatcher/composer.json
@@ -21,7 +21,7 @@
     "require-dev": {
         "symfony/dependency-injection": "~2.6|~3.0.0",
         "symfony/expression-language": "~2.6|~3.0.0",
-        "symfony/config": "~2.0,>=2.0.5|~3.0.0",
+        "symfony/config": "^2.0.5|~3.0.0",
         "symfony/stopwatch": "~2.3|~3.0.0",
         "psr/log": "~1.0"
     },
diff --git a/symfony/event-dispatcher/phpunit.xml.dist 
b/symfony/event-dispatcher/phpunit.xml.dist
index ae0586e..b3ad1bd 100644
--- a/symfony/event-dispatcher/phpunit.xml.dist
+++ b/symfony/event-dispatcher/phpunit.xml.dist
@@ -5,6 +5,8 @@
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
 >
     <php>
         <ini name="error_reporting" value="-1" />
diff --git a/symfony/polyfill-mbstring/Mbstring.php 
b/symfony/polyfill-mbstring/Mbstring.php
index 934cfcf..97e8c9b 100644
--- a/symfony/polyfill-mbstring/Mbstring.php
+++ b/symfony/polyfill-mbstring/Mbstring.php
@@ -147,6 +147,9 @@
 
         if ('UTF-8' === $encoding) {
             $encoding = null;
+            if (!preg_match('//u', $s)) {
+                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+            }
         } else {
             $s = iconv($encoding, 'UTF-8//IGNORE', $s);
         }
@@ -336,10 +339,9 @@
 
     public static function mb_strlen($s, $encoding = null)
     {
-        switch ($encoding = self::getEncoding($encoding)) {
-            case 'ASCII':
-            case 'CP850':
-                return strlen($s);
+        $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strlen($s);
         }
 
         return @iconv_strlen($s, $encoding);
@@ -348,6 +350,9 @@
     public static function mb_strpos($haystack, $needle, $offset = 0, 
$encoding = null)
     {
         $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strpos($haystack, $needle, $offset);
+        }
 
         if ('' === $needle .= '') {
             trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
@@ -361,6 +366,9 @@
     public static function mb_strrpos($haystack, $needle, $offset = 0, 
$encoding = null)
     {
         $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrpos($haystack, $needle, $offset);
+        }
 
         if ($offset != (int) $offset) {
             $offset = 0;
@@ -400,6 +408,9 @@
     public static function mb_substr($s, $start, $length = null, $encoding = 
null)
     {
         $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return substr($s, $start, null === $length ? 2147483647 : $length);
+        }
 
         if ($start < 0) {
             $start = iconv_strlen($s, $encoding) + $start;
@@ -438,6 +449,9 @@
     public static function mb_strrchr($haystack, $needle, $part = false, 
$encoding = null)
     {
         $encoding = self::getEncoding($encoding);
+        if ('CP850' === $encoding || 'ASCII' === $encoding) {
+            return strrchr($haystack, $needle, $part);
+        }
         $needle = self::mb_substr($needle, 0, 1, $encoding);
         $pos = iconv_strrpos($haystack, $needle, $encoding);
 
diff --git a/symfony/polyfill-mbstring/composer.json 
b/symfony/polyfill-mbstring/composer.json
index 24eefbd..48fc3dd 100644
--- a/symfony/polyfill-mbstring/composer.json
+++ b/symfony/polyfill-mbstring/composer.json
@@ -28,7 +28,7 @@
     "minimum-stability": "dev",
     "extra": {
         "branch-alias": {
-            "dev-master": "1.3-dev"
+            "dev-master": "1.4-dev"
         }
     }
 }
diff --git a/symfony/yaml/Escaper.php b/symfony/yaml/Escaper.php
index a74f14d..94bb392 100644
--- a/symfony/yaml/Escaper.php
+++ b/symfony/yaml/Escaper.php
@@ -33,13 +33,15 @@
                                      "\x08",  "\x09",  "\x0a",  "\x0b",  
"\x0c",  "\x0d",  "\x0e",  "\x0f",
                                      "\x10",  "\x11",  "\x12",  "\x13",  
"\x14",  "\x15",  "\x16",  "\x17",
                                      "\x18",  "\x19",  "\x1a",  "\x1b",  
"\x1c",  "\x1d",  "\x1e",  "\x1f",
-                                     "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", 
"\xe2\x80\xa9");
+                                     "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", 
"\xe2\x80\xa9",
+                               );
     private static $escaped = array('\\\\', '\\"', '\\\\', '\\"',
                                      '\\0',   '\\x01', '\\x02', '\\x03', 
'\\x04', '\\x05', '\\x06', '\\a',
                                      '\\b',   '\\t',   '\\n',   '\\v',   
'\\f',   '\\r',   '\\x0e', '\\x0f',
                                      '\\x10', '\\x11', '\\x12', '\\x13', 
'\\x14', '\\x15', '\\x16', '\\x17',
                                      '\\x18', '\\x19', '\\x1a', '\\e',   
'\\x1c', '\\x1d', '\\x1e', '\\x1f',
-                                     '\\N', '\\_', '\\L', '\\P');
+                                     '\\N', '\\_', '\\L', '\\P',
+                              );
 
     /**
      * Determines if a PHP value would require double quoting in YAML.
@@ -50,7 +52,7 @@
      */
     public static function requiresDoubleQuoting($value)
     {
-        return preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
+        return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', 
$value);
     }
 
     /**
@@ -82,7 +84,7 @@
 
         // Determines if the PHP value contains any single characters that 
would
         // cause it to require single quoting in YAML.
-        return preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? 
| < > = ! % @ ` ]/x', $value);
+        return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ 
\- ? | < > = ! % @ ` ]/x', $value);
     }
 
     /**
diff --git a/symfony/yaml/Exception/ParseException.php 
b/symfony/yaml/Exception/ParseException.php
index b74eb91..ef36cfb 100644
--- a/symfony/yaml/Exception/ParseException.php
+++ b/symfony/yaml/Exception/ParseException.php
@@ -26,11 +26,11 @@
     /**
      * Constructor.
      *
-     * @param string     $message    The error message
-     * @param int        $parsedLine The line where the error occurred
-     * @param int        $snippet    The snippet of code near the problem
-     * @param string     $parsedFile The file name where the error occurred
-     * @param \Exception $previous   The previous exception
+     * @param string          $message    The error message
+     * @param int             $parsedLine The line where the error occurred
+     * @param string|null     $snippet    The snippet of code near the problem
+     * @param string|null     $parsedFile The file name where the error 
occurred
+     * @param \Exception|null $previous   The previous exception
      */
     public function __construct($message, $parsedLine = -1, $snippet = null, 
$parsedFile = null, \Exception $previous = null)
     {
@@ -123,7 +123,7 @@
         }
 
         if (null !== $this->parsedFile) {
-            if (PHP_VERSION_ID >= 50400) {
+            if (\PHP_VERSION_ID >= 50400) {
                 $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
             } else {
                 $jsonOptions = 0;
diff --git a/symfony/yaml/Inline.php b/symfony/yaml/Inline.php
index 74d23be..dade755 100644
--- a/symfony/yaml/Inline.php
+++ b/symfony/yaml/Inline.php
@@ -21,7 +21,7 @@
  */
 class Inline
 {
-    const REGEX_QUOTED_STRING = 
'(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')';
+    const REGEX_QUOTED_STRING = 
'(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
 
     private static $exceptionOnInvalidType = false;
     private static $objectSupport = false;
@@ -149,8 +149,8 @@
             case Escaper::requiresDoubleQuoting($value):
                 return Escaper::escapeWithDoubleQuotes($value);
             case Escaper::requiresSingleQuoting($value):
-            case preg_match(self::getHexRegex(), $value):
-            case preg_match(self::getTimestampRegex(), $value):
+            case Parser::preg_match(self::getHexRegex(), $value):
+            case Parser::preg_match(self::getTimestampRegex(), $value):
                 return Escaper::escapeWithSingleQuotes($value);
             default:
                 return $value;
@@ -212,12 +212,12 @@
     /**
      * Parses a YAML scalar.
      *
-     * @param string $scalar
-     * @param string $delimiters
-     * @param array  $stringDelimiters
-     * @param int    &$i
-     * @param bool   $evaluate
-     * @param array  $references
+     * @param string   $scalar
+     * @param string[] $delimiters
+     * @param string[] $stringDelimiters
+     * @param int      &$i
+     * @param bool     $evaluate
+     * @param array    $references
      *
      * @return string
      *
@@ -244,10 +244,10 @@
                 $i += strlen($output);
 
                 // remove comments
-                if (preg_match('/[ \t]+#/', $output, $match, 
PREG_OFFSET_CAPTURE)) {
+                if (Parser::preg_match('/[ \t]+#/', $output, $match, 
PREG_OFFSET_CAPTURE)) {
                     $output = substr($output, 0, $match[0][1]);
                 }
-            } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', 
substr($scalar, $i), $match)) {
+            } elseif (Parser::preg_match('/^(.+?)('.implode('|', 
$delimiters).')/', substr($scalar, $i), $match)) {
                 $output = $match[1];
                 $i += strlen($output);
             } else {
@@ -282,7 +282,7 @@
      */
     private static function parseQuotedScalar($scalar, &$i)
     {
-        if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, 
$i), $match)) {
+        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', 
substr($scalar, $i), $match)) {
             throw new ParseException(sprintf('Malformed inline YAML string: 
%s.', substr($scalar, $i)));
         }
 
@@ -453,7 +453,7 @@
      * @param string $scalar
      * @param array  $references
      *
-     * @return string A YAML string
+     * @return mixed The evaluated YAML string
      *
      * @throws ParseException when object parsing support was disabled and the 
parser detected a PHP object or when a reference could not be resolved
      */
@@ -530,16 +530,16 @@
 
                         return '0' == $scalar[1] ? octdec($scalar) : 
(((string) $raw === (string) $cast) ? $cast : $raw);
                     case is_numeric($scalar):
-                    case preg_match(self::getHexRegex(), $scalar):
+                    case Parser::preg_match(self::getHexRegex(), $scalar):
                         return '0x' === $scalar[0].$scalar[1] ? 
hexdec($scalar) : (float) $scalar;
                     case '.inf' === $scalarLower:
                     case '.nan' === $scalarLower:
                         return -log(0);
                     case '-.inf' === $scalarLower:
                         return log(0);
-                    case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
+                    case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', 
$scalar):
                         return (float) str_replace(',', '', $scalar);
-                    case preg_match(self::getTimestampRegex(), $scalar):
+                    case Parser::preg_match(self::getTimestampRegex(), 
$scalar):
                         $timeZone = date_default_timezone_get();
                         date_default_timezone_set('UTC');
                         $time = strtotime($scalar);
diff --git a/symfony/yaml/Parser.php b/symfony/yaml/Parser.php
index 97db3af..96a85a8 100644
--- a/symfony/yaml/Parser.php
+++ b/symfony/yaml/Parser.php
@@ -61,26 +61,60 @@
      */
     public function parse($value, $exceptionOnInvalidType = false, 
$objectSupport = false, $objectForMap = false)
     {
-        if (!preg_match('//u', $value)) {
+        if (false === preg_match('//u', $value)) {
             throw new ParseException('The YAML value does not appear to be 
valid UTF-8.');
         }
-        $this->currentLineNb = -1;
-        $this->currentLine = '';
-        $value = $this->cleanup($value);
-        $this->lines = explode("\n", $value);
 
-        if (null === $this->totalNumberOfLines) {
-            $this->totalNumberOfLines = count($this->lines);
-        }
+        $this->refs = array();
+
+        $mbEncoding = null;
+        $e = null;
+        $data = null;
 
         if (2 /* MB_OVERLOAD_STRING */ & (int) 
ini_get('mbstring.func_overload')) {
             $mbEncoding = mb_internal_encoding();
             mb_internal_encoding('UTF-8');
         }
 
+        try {
+            $data = $this->doParse($value, $exceptionOnInvalidType, 
$objectSupport, $objectForMap);
+        } catch (\Exception $e) {
+        } catch (\Throwable $e) {
+        }
+
+        if (null !== $mbEncoding) {
+            mb_internal_encoding($mbEncoding);
+        }
+
+        $this->lines = array();
+        $this->currentLine = '';
+        $this->refs = array();
+        $this->skippedLineNumbers = array();
+        $this->locallySkippedLineNumbers = array();
+
+        if (null !== $e) {
+            throw $e;
+        }
+
+        return $data;
+    }
+
+    private function doParse($value, $exceptionOnInvalidType = false, 
$objectSupport = false, $objectForMap = false)
+    {
+        $this->currentLineNb = -1;
+        $this->currentLine = '';
+        $value = $this->cleanup($value);
+        $this->lines = explode("\n", $value);
+        $this->locallySkippedLineNumbers = array();
+
+        if (null === $this->totalNumberOfLines) {
+            $this->totalNumberOfLines = count($this->lines);
+        }
+
         $data = array();
         $context = null;
         $allowOverwrite = false;
+
         while ($this->moveToNextLine()) {
             if ($this->isCurrentLineEmpty()) {
                 continue;
@@ -92,13 +126,13 @@
             }
 
             $isRef = $mergeNode = false;
-            if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', 
$this->currentLine, $values)) {
+            if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', 
rtrim($this->currentLine), $values)) {
                 if ($context && 'mapping' == $context) {
                     throw new ParseException('You cannot define a sequence 
item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                 }
                 $context = 'sequence';
 
-                if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) 
*(?P<value>.*)#u', $values['value'], $matches)) {
+                if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ 
]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                     $isRef = $matches['ref'];
                     $values['value'] = $matches['value'];
                 }
@@ -108,7 +142,7 @@
                     $data[] = $this->parseBlock($this->getRealCurrentLineNb() 
+ 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, 
$objectSupport, $objectForMap);
                 } else {
                     if (isset($values['leadspaces'])
-                        && 
preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) 
*\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
+                        && 
self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) 
*\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
                     ) {
                         // this is a compact notation element, add to next 
block and parse
                         $block = $values['value'];
@@ -124,7 +158,10 @@
                 if ($isRef) {
                     $this->refs[$isRef] = end($data);
                 }
-            } elseif (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ 
\'"\[\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->currentLine, $values) && 
(false === strpos($values['key'], ' #') || in_array($values['key'][0], 
array('"', "'")))) {
+            } elseif (
+                self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ 
\'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
+                && (false === strpos($values['key'], ' #') || 
in_array($values['key'][0], array('"', "'")))
+            ) {
                 if ($context && 'sequence' == $context) {
                     throw new ParseException('You cannot define a mapping item 
when in a sequence', $this->currentLineNb + 1, $this->currentLine);
                 }
@@ -161,11 +198,7 @@
                             throw new ParseException('YAML merge keys used 
with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, 
$this->currentLine);
                         }
 
-                        foreach ($refValue as $key => $value) {
-                            if (!isset($data[$key])) {
-                                $data[$key] = $value;
-                            }
-                        }
+                        $data += $refValue; // array union
                     } else {
                         if (isset($values['value']) && $values['value'] !== 
'') {
                             $value = $values['value'];
@@ -187,23 +220,15 @@
                                     throw new ParseException('Merge items must 
be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
                                 }
 
-                                foreach ($parsedItem as $key => $value) {
-                                    if (!isset($data[$key])) {
-                                        $data[$key] = $value;
-                                    }
-                                }
+                                $data += $parsedItem; // array union
                             }
                         } else {
                             // If the value associated with the key is a 
single mapping node, each of its key/value pairs is inserted into the
                             // current mapping, unless the key already exists 
in it.
-                            foreach ($parsed as $key => $value) {
-                                if (!isset($data[$key])) {
-                                    $data[$key] = $value;
-                                }
-                            }
+                            $data += $parsed; // array union
                         }
                     }
-                } elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ 
]+) *(?P<value>.*)#u', $values['value'], $matches)) {
+                } elseif (isset($values['value']) && 
self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], 
$matches)) {
                     $isRef = $matches['ref'];
                     $values['value'] = $matches['value'];
                 }
@@ -255,39 +280,11 @@
                         throw $e;
                     }
 
-                    if (isset($mbEncoding)) {
-                        mb_internal_encoding($mbEncoding);
-                    }
-
                     return $value;
                 }
 
-                switch (preg_last_error()) {
-                    case PREG_INTERNAL_ERROR:
-                        $error = 'Internal PCRE error.';
-                        break;
-                    case PREG_BACKTRACK_LIMIT_ERROR:
-                        $error = 'pcre.backtrack_limit reached.';
-                        break;
-                    case PREG_RECURSION_LIMIT_ERROR:
-                        $error = 'pcre.recursion_limit reached.';
-                        break;
-                    case PREG_BAD_UTF8_ERROR:
-                        $error = 'Malformed UTF-8 data.';
-                        break;
-                    case PREG_BAD_UTF8_OFFSET_ERROR:
-                        $error = 'Offset doesn\'t correspond to the begin of a 
valid UTF-8 code point.';
-                        break;
-                    default:
-                        $error = 'Unable to parse.';
-                }
-
-                throw new ParseException($error, $this->getRealCurrentLineNb() 
+ 1, $this->currentLine);
+                throw new ParseException('Unable to parse.', 
$this->getRealCurrentLineNb() + 1, $this->currentLine);
             }
-        }
-
-        if (isset($mbEncoding)) {
-            mb_internal_encoding($mbEncoding);
         }
 
         if ($objectForMap && !is_object($data) && 'mapping' === $context) {
@@ -318,7 +315,7 @@
         $parser = new self($offset, $this->totalNumberOfLines, 
$skippedLineNumbers);
         $parser->refs = &$this->refs;
 
-        return $parser->parse($yaml, $exceptionOnInvalidType, $objectSupport, 
$objectForMap);
+        return $parser->doParse($yaml, $exceptionOnInvalidType, 
$objectSupport, $objectForMap);
     }
 
     /**
@@ -527,7 +524,7 @@
             return $this->refs[$value];
         }
 
-        if (preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, 
$matches)) {
+        if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', 
$value, $matches)) {
             $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] 
: '';
 
             return $this->parseBlockScalar($matches['separator'], 
preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
@@ -582,7 +579,7 @@
 
         // determine indentation if not specified
         if (0 === $indentation) {
-            if (preg_match('/^ +/', $this->currentLine, $matches)) {
+            if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
                 $indentation = strlen($matches[0]);
             }
         }
@@ -593,7 +590,7 @@
             while (
                 $notEOF && (
                     $isCurrentLineBlank ||
-                    preg_match($pattern, $this->currentLine, $matches)
+                    self::preg_match($pattern, $this->currentLine, $matches)
                 )
             ) {
                 if ($isCurrentLineBlank && strlen($this->currentLine) > 
$indentation) {
@@ -626,7 +623,7 @@
             $previousLineIndented = false;
             $previousLineBlank = false;
 
-            for ($i = 0; $i < count($blockLines); ++$i) {
+            for ($i = 0, $blockLinesCount = count($blockLines); $i < 
$blockLinesCount; ++$i) {
                 if ('' === $blockLines[$i]) {
                     $text .= "\n";
                     $previousLineIndented = false;
@@ -681,10 +678,7 @@
             return false;
         }
 
-        $ret = false;
-        if ($this->getCurrentLineIndentation() > $currentIndentation) {
-            $ret = true;
-        }
+        $ret = $this->getCurrentLineIndentation() > $currentIndentation;
 
         $this->moveToPreviousLine();
 
@@ -785,14 +779,7 @@
             return false;
         }
 
-        $ret = false;
-        if (
-            $this->getCurrentLineIndentation() == $currentIndentation
-            &&
-            $this->isStringUnIndentedCollectionItem()
-        ) {
-            $ret = true;
-        }
+        $ret = $this->getCurrentLineIndentation() === $currentIndentation && 
$this->isStringUnIndentedCollectionItem();
 
         $this->moveToPreviousLine();
 
@@ -816,6 +803,48 @@
      */
     private function isBlockScalarHeader()
     {
-        return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', 
$this->currentLine);
+        return (bool) 
self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', 
$this->currentLine);
+    }
+
+    /**
+     * A local wrapper for `preg_match` which will throw a ParseException if 
there
+     * is an internal error in the PCRE engine.
+     *
+     * This avoids us needing to check for "false" every time PCRE is used
+     * in the YAML engine
+     *
+     * @throws ParseException on a PCRE internal error
+     *
+     * @see preg_last_error()
+     *
+     * @internal
+     */
+    public static function preg_match($pattern, $subject, &$matches = null, 
$flags = 0, $offset = 0)
+    {
+        if (false === $ret = preg_match($pattern, $subject, $matches, $flags, 
$offset)) {
+            switch (preg_last_error()) {
+                case PREG_INTERNAL_ERROR:
+                    $error = 'Internal PCRE error.';
+                    break;
+                case PREG_BACKTRACK_LIMIT_ERROR:
+                    $error = 'pcre.backtrack_limit reached.';
+                    break;
+                case PREG_RECURSION_LIMIT_ERROR:
+                    $error = 'pcre.recursion_limit reached.';
+                    break;
+                case PREG_BAD_UTF8_ERROR:
+                    $error = 'Malformed UTF-8 data.';
+                    break;
+                case PREG_BAD_UTF8_OFFSET_ERROR:
+                    $error = 'Offset doesn\'t correspond to the begin of a 
valid UTF-8 code point.';
+                    break;
+                default:
+                    $error = 'Error.';
+            }
+
+            throw new ParseException($error);
+        }
+
+        return $ret;
     }
 }
diff --git a/symfony/yaml/Tests/DumperTest.php 
b/symfony/yaml/Tests/DumperTest.php
index 84069d8..6a1b3ac 100644
--- a/symfony/yaml/Tests/DumperTest.php
+++ b/symfony/yaml/Tests/DumperTest.php
@@ -11,10 +11,11 @@
 
 namespace Symfony\Component\Yaml\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\Yaml\Parser;
 use Symfony\Component\Yaml\Dumper;
 
-class DumperTest extends \PHPUnit_Framework_TestCase
+class DumperTest extends TestCase
 {
     protected $parser;
     protected $dumper;
@@ -209,23 +210,25 @@
     public function getEscapeSequences()
     {
         return array(
-            'null' => array("\t\\0", '"\t\\\\0"'),
-            'bell' => array("\t\\a", '"\t\\\\a"'),
-            'backspace' => array("\t\\b", '"\t\\\\b"'),
-            'horizontal-tab' => array("\t\\t", '"\t\\\\t"'),
-            'line-feed' => array("\t\\n", '"\t\\\\n"'),
-            'vertical-tab' => array("\t\\v", '"\t\\\\v"'),
-            'form-feed' => array("\t\\f", '"\t\\\\f"'),
-            'carriage-return' => array("\t\\r", '"\t\\\\r"'),
-            'escape' => array("\t\\e", '"\t\\\\e"'),
-            'space' => array("\t\\ ", '"\t\\\\ "'),
-            'double-quote' => array("\t\\\"", '"\t\\\\\\""'),
-            'slash' => array("\t\\/", '"\t\\\\/"'),
-            'backslash' => array("\t\\\\", '"\t\\\\\\\\"'),
-            'next-line' => array("\t\\N", '"\t\\\\N"'),
-            'non-breaking-space' => array("\t\\�", '"\t\\\\�"'),
-            'line-separator' => array("\t\\L", '"\t\\\\L"'),
-            'paragraph-separator' => array("\t\\P", '"\t\\\\P"'),
+            'empty string' => array('', "''"),
+            'null' => array("\x0", '"\\0"'),
+            'bell' => array("\x7", '"\\a"'),
+            'backspace' => array("\x8", '"\\b"'),
+            'horizontal-tab' => array("\t", '"\\t"'),
+            'line-feed' => array("\n", '"\\n"'),
+            'vertical-tab' => array("\v", '"\\v"'),
+            'form-feed' => array("\xC", '"\\f"'),
+            'carriage-return' => array("\r", '"\\r"'),
+            'escape' => array("\x1B", '"\\e"'),
+            'space' => array(' ', "' '"),
+            'double-quote' => array('"', "'\"'"),
+            'slash' => array('/', '/'),
+            'backslash' => array('\\', '\\'),
+            'next-line' => array("\xC2\x85", '"\\N"'),
+            'non-breaking-space' => array("\xc2\xa0", '"\\_"'),
+            'line-separator' => array("\xE2\x80\xA8", '"\\L"'),
+            'paragraph-separator' => array("\xE2\x80\xA9", '"\\P"'),
+            'colon' => array(':', "':'"),
         );
     }
 
diff --git a/symfony/yaml/Tests/Fixtures/sfMergeKey.yml 
b/symfony/yaml/Tests/Fixtures/sfMergeKey.yml
index 4b67d34..59f6125 100644
--- a/symfony/yaml/Tests/Fixtures/sfMergeKey.yml
+++ b/symfony/yaml/Tests/Fixtures/sfMergeKey.yml
@@ -10,9 +10,11 @@
         a: Steve
         b: Clark
         c: Brian
+        e: notnull
     bar:
         a: before
         d: other
+        e: ~
         <<: *foo
         b: new
         x: Oren
@@ -46,13 +48,13 @@
             <<: *nestedref
 php: |
     array(
-        'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'),
-        'bar' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => 
array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'),
+        'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 
'notnull'),
+        'bar' => array('a' => 'before', 'd' => 'other', 'e' => null, 'b' => 
'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'),
         'duplicate' => array('foo' => 'bar'),
         'foo2' => array('a' => 'Ballmer'),
         'ding' => array('fi', 'fei', 'fo', 'fam'),
-        'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 
'fei', 'fo', 'fam', 'isit' => 'tested'),
-        'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 
'fei', 'fo', 'fam'),
+        'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' 
=> 'notnull', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'),
+        'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 
'notnull', 'fi', 'fei', 'fo', 'fam'),
         'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)),
         'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 
'Doug', 'z' => array('p' => 12345))
     )
diff --git a/symfony/yaml/Tests/InlineTest.php 
b/symfony/yaml/Tests/InlineTest.php
index bc7921f..7ca692d 100644
--- a/symfony/yaml/Tests/InlineTest.php
+++ b/symfony/yaml/Tests/InlineTest.php
@@ -11,9 +11,10 @@
 
 namespace Symfony\Component\Yaml\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\Yaml\Inline;
 
-class InlineTest extends \PHPUnit_Framework_TestCase
+class InlineTest extends TestCase
 {
     /**
      * @dataProvider getTestsForParse
@@ -192,27 +193,42 @@
 
     /**
      * @group legacy
-     * @dataProvider getReservedIndicators
+     * @expectedDeprecation Not quoting the scalar "@foo " starting with "@" 
is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
      * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
      */
-    public function 
testParseUnquotedScalarStartingWithReservedIndicator($indicator)
+    public function testParseUnquotedScalarStartingWithReservedAtIndicator()
     {
-        Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
-    }
-
-    public function getReservedIndicators()
-    {
-        return array(array('@'), array('`'));
+        Inline::parse('{ foo: @foo }');
     }
 
     /**
      * @group legacy
-     * @dataProvider getScalarIndicators
+     * @expectedDeprecation Not quoting the scalar "`foo " starting with "`" 
is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
      * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
      */
-    public function 
testParseUnquotedScalarStartingWithScalarIndicator($indicator)
+    public function 
testParseUnquotedScalarStartingWithReservedBacktickIndicator()
     {
-        Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
+        Inline::parse('{ foo: `foo }');
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation Not quoting the scalar "|foo " starting with "|" 
is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
+     * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
+     */
+    public function testParseUnquotedScalarStartingWithLiteralStyleIndicator()
+    {
+        Inline::parse('{ foo: |foo }');
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation Not quoting the scalar ">foo " starting with ">" 
is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
+     * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
+     */
+    public function testParseUnquotedScalarStartingWithFoldedStyleIndicator()
+    {
+        Inline::parse('{ foo: >foo }');
     }
 
     public function getScalarIndicators()
@@ -457,4 +473,25 @@
     {
         Inline::parse('{this, is not, supported}');
     }
+
+    public function testVeryLongQuotedStrings()
+    {
+        $longStringWithQuotes = str_repeat("x\r\n\\\"x\"x", 1000);
+
+        $yamlString = Inline::dump(array('longStringWithQuotes' => 
$longStringWithQuotes));
+        $arrayFromYaml = Inline::parse($yamlString);
+
+        $this->assertEquals($longStringWithQuotes, 
$arrayFromYaml['longStringWithQuotes']);
+    }
+
+    public function testBooleanMappingKeysAreConvertedToStrings()
+    {
+        $this->assertSame(array('false' => 'foo'), Inline::parse('{false: 
foo}'));
+        $this->assertSame(array('true' => 'foo'), Inline::parse('{true: 
foo}'));
+    }
+
+    public function testTheEmptyStringIsAValidMappingKey()
+    {
+        $this->assertSame(array('' => 'foo'), Inline::parse('{ "": foo }'));
+    }
 }
diff --git a/symfony/yaml/Tests/ParseExceptionTest.php 
b/symfony/yaml/Tests/ParseExceptionTest.php
index e4eb9c9..b7797fb 100644
--- a/symfony/yaml/Tests/ParseExceptionTest.php
+++ b/symfony/yaml/Tests/ParseExceptionTest.php
@@ -11,14 +11,15 @@
 
 namespace Symfony\Component\Yaml\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\Yaml\Exception\ParseException;
 
-class ParseExceptionTest extends \PHPUnit_Framework_TestCase
+class ParseExceptionTest extends TestCase
 {
     public function testGetMessage()
     {
         $exception = new ParseException('Error message', 42, 'foo: bar', 
'/var/www/app/config.yml');
-        if (PHP_VERSION_ID >= 50400) {
+        if (\PHP_VERSION_ID >= 50400) {
             $message = 'Error message in "/var/www/app/config.yml" at line 42 
(near "foo: bar")';
         } else {
             $message = 'Error message in "\\/var\\/www\\/app\\/config.yml" at 
line 42 (near "foo: bar")';
@@ -30,7 +31,7 @@
     public function testGetMessageWithUnicodeInFilename()
     {
         $exception = new ParseException('Error message', 42, 'foo: bar', 
'äöü.yml');
-        if (PHP_VERSION_ID >= 50400) {
+        if (\PHP_VERSION_ID >= 50400) {
             $message = 'Error message in "äöü.yml" at line 42 (near "foo: 
bar")';
         } else {
             $message = 'Error message in "\u00e4\u00f6\u00fc.yml" at line 42 
(near "foo: bar")';
diff --git a/symfony/yaml/Tests/ParserTest.php 
b/symfony/yaml/Tests/ParserTest.php
index 6327fd0..2c3a6e0 100644
--- a/symfony/yaml/Tests/ParserTest.php
+++ b/symfony/yaml/Tests/ParserTest.php
@@ -11,11 +11,13 @@
 
 namespace Symfony\Component\Yaml\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\Yaml\Yaml;
 use Symfony\Component\Yaml\Parser;
 
-class ParserTest extends \PHPUnit_Framework_TestCase
+class ParserTest extends TestCase
 {
+    /** @var Parser */
     protected $parser;
 
     protected function setUp()
@@ -1156,10 +1158,12 @@
      */
     public function 
testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml)
     {
-        $this->setExpectedException(
-            '\Symfony\Component\Yaml\Exception\ParseException',
-            sprintf('Unexpected characters near "," at line %d (near "bar: 
"123",").', $lineNumber)
-        );
+        if (method_exists($this, 'expectException')) {
+            
$this->expectException('\Symfony\Component\Yaml\Exception\ParseException');
+            $this->expectExceptionMessage(sprintf('Unexpected characters near 
"," at line %d (near "bar: "123",").', $lineNumber));
+        } else {
+            
$this->setExpectedException('\Symfony\Component\Yaml\Exception\ParseException', 
sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', 
$lineNumber));
+        }
 
         $this->parser->parse($yaml);
     }
@@ -1216,6 +1220,38 @@
             ),
         );
     }
+
+    public function testCanParseVeryLongValue()
+    {
+        $longStringWithSpaces = str_repeat('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ', 
20000);
+        $trickyVal = array('x' => $longStringWithSpaces);
+
+        $yamlString = Yaml::dump($trickyVal);
+        $arrayFromYaml = $this->parser->parse($yamlString);
+
+        $this->assertEquals($trickyVal, $arrayFromYaml);
+    }
+
+    /**
+     * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+     * @expectedExceptionMessage Reference "foo" does not exist at line 2
+     */
+    public function testParserCleansUpReferencesBetweenRuns()
+    {
+        $yaml = <<<YAML
+foo: &foo
+    baz: foobar
+bar:
+    <<: *foo
+YAML;
+        $this->parser->parse($yaml);
+
+        $yaml = <<<YAML
+bar:
+    <<: *foo
+YAML;
+        $this->parser->parse($yaml);
+    }
 }
 
 class B
diff --git a/symfony/yaml/Tests/YamlTest.php b/symfony/yaml/Tests/YamlTest.php
index 883ee83..9e776ca 100644
--- a/symfony/yaml/Tests/YamlTest.php
+++ b/symfony/yaml/Tests/YamlTest.php
@@ -11,9 +11,10 @@
 
 namespace Symfony\Component\Yaml\Tests;
 
+use PHPUnit\Framework\TestCase;
 use Symfony\Component\Yaml\Yaml;
 
-class YamlTest extends \PHPUnit_Framework_TestCase
+class YamlTest extends TestCase
 {
     public function testParseAndDump()
     {
diff --git a/symfony/yaml/phpunit.xml.dist b/symfony/yaml/phpunit.xml.dist
index 6bdbea1..7c732f8 100644
--- a/symfony/yaml/phpunit.xml.dist
+++ b/symfony/yaml/phpunit.xml.dist
@@ -5,6 +5,8 @@
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
 >
     <php>
         <ini name="error_reporting" value="-1" />
diff --git a/wikimedia/omnimail-silverpop/src/Requests/ExportListRequest.php 
b/wikimedia/omnimail-silverpop/src/Requests/ExportListRequest.php
index 9bb7d56..ffb87fb 100644
--- a/wikimedia/omnimail-silverpop/src/Requests/ExportListRequest.php
+++ b/wikimedia/omnimail-silverpop/src/Requests/ExportListRequest.php
@@ -23,6 +23,31 @@
   protected $groupIdentifier;
 
   /**
+   * Specifies which contacts to export. Valid values are:
+   * - ALL – export entire database. System columns will not be exported by 
default.
+   * - OPT_IN – export only currently opted-in contacts.
+   * - OPT_OUT – export only currently opted-out contacts.
+   * - UNDELIVERABLE – export only contacts who are currently marked as 
undeliverable.
+   *
+   * @var string
+   */
+  protected $exportType;
+
+  /**
+   * @return string
+   */
+  public function getExportType() {
+    return $this->exportType;
+  }
+
+  /**
+   * @param string $exportType
+   */
+  public function setExportType($exportType) {
+    $this->exportType = $exportType;
+  }
+
+  /**
    * @return mixed
    */
   public function getGroupIdentifier() {
@@ -108,7 +133,8 @@
     $result = $this->silverPop->exportList(
       $this->getGroupIdentifier(),
       $this->getStartTimeStamp(),
-      $this->getEndTimeStamp()
+      $this->getEndTimeStamp(),
+      $this->getExportType()
     );
     $this->setRetrievalParameters(array(
       'jobId' => (string) $result['jobId'],
@@ -129,6 +155,7 @@
       'includeTest' => FALSE,
       'startTimeStamp' => strtotime('1 week ago'),
       'endTimeStamp' => strtotime('now'),
+      'exportType' => 'ALL'
     );
   }
 
diff --git a/wikimedia/omnimail-silverpop/src/Responses/Contact.php 
b/wikimedia/omnimail-silverpop/src/Responses/Contact.php
index b2ae90a..75dab07 100644
--- a/wikimedia/omnimail-silverpop/src/Responses/Contact.php
+++ b/wikimedia/omnimail-silverpop/src/Responses/Contact.php
@@ -75,7 +75,7 @@
    * @return false|string
    */
   public function getOptOutIsoDateTime() {
-    return (date('Y-m-d H:i:s', $this->getOptOutTimestamp()));
+    return (empty($this->getOptOutTimestamp()) ? FALSE : date('Y-m-d H:i:s', 
$this->getOptOutTimestamp()));
   }
 
   /**
diff --git a/wikimedia/smash-pig/Core/Configuration.php 
b/wikimedia/smash-pig/Core/Configuration.php
index 3923c7a..bf59f2a 100644
--- a/wikimedia/smash-pig/Core/Configuration.php
+++ b/wikimedia/smash-pig/Core/Configuration.php
@@ -104,15 +104,12 @@
         * Obtain a value from the configuration. If the key does not exist 
this will throw an
         * exception.
         *
-        * @param string $node        Parameter node to obtain. If this 
contains '/' it is assumed that the
+        * @param string $path Parameter node to obtain. If this contains '/' 
it is assumed that the
         *                            value is contained under additional keys.
-        * @param bool $returnRef     If true will return a reference to the 
configuration node. This will
-        *                            mean that any modifications to the node 
will be stored in RAM for the
-        *                            duration of the session.
         * @return mixed
         * @throws ConfigurationKeyException
         */
-       public function &val( $node, $returnRef = false ) {
+       public function val( $path ) {
                /*
                 * Magic "/" returns the entire configuration tree.
                 *
@@ -122,34 +119,22 @@
                 * Note: Never log this tree insecurely, it will contain 
processor
                 * credentials and other sensitive information.
                 */
-               if ( $node === '/' ) {
-                       if ( $returnRef ) {
-                               // TODO: Don't offer a return-by-reference.
-                               $options = &$this->options;
-                       } else {
-                               $options = $this->options;
-                       }
-                       return $options;
+               if ( $path === '/' ) {
+                       return $this->options;
                }
 
-               $keys = explode( '/', $node );
+               $segments = explode( '/', $path );
 
-               $croot = & $this->options;
-               foreach ( $keys as $key ) {
-                       if ( array_key_exists( $key, $croot ) ) {
-                               $croot = & $croot[ $key ];
+               $currentNode = $this->options;
+               foreach ( $segments as $segment ) {
+                       if ( array_key_exists( $segment, $currentNode ) ) {
+                               $currentNode = $currentNode[$segment];
                        } else {
-                               throw new ConfigurationKeyException( 
"Configuration key '{$node}' does not exist.", $node );
+                               throw new ConfigurationKeyException( 
"Configuration key '{$path}' does not exist.", $path );
                        }
                }
 
-               if ( $returnRef ) {
-                       return $croot;
-               } else {
-                       // Dereference the variable
-                       $obj = $croot;
-                       return $obj;
-               }
+               return $currentNode;
        }
 
        /**
@@ -208,7 +193,7 @@
         */
        public function nodeExists( $node ) {
                try {
-                       $this->val( $node, true );
+                       $this->val( $node );
                        return true;
                } catch ( ConfigurationKeyException $ex ) {
                        return false;
diff --git a/wikimedia/smash-pig/Core/DataFiles/AuditParser.php 
b/wikimedia/smash-pig/Core/DataFiles/AuditParser.php
new file mode 100644
index 0000000..5dd777e
--- /dev/null
+++ b/wikimedia/smash-pig/Core/DataFiles/AuditParser.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace SmashPig\Core\DataFiles;
+
+interface AuditParser {
+
+       /**
+        * Parse an audit file and normalize records
+        *
+        * @param string $path Full path of the file to be parsed
+        * @return array of donation, refund, and chargeback records
+        */
+       public function parseFile( $path );
+}
diff --git a/wikimedia/smash-pig/Core/DataStores/QueueWrapper.php 
b/wikimedia/smash-pig/Core/DataStores/QueueWrapper.php
index ac504f6..537038d 100644
--- a/wikimedia/smash-pig/Core/DataStores/QueueWrapper.php
+++ b/wikimedia/smash-pig/Core/DataStores/QueueWrapper.php
@@ -30,7 +30,7 @@
         $key = "data-store/$queueName";
 
         // Examine the config node for a queue name
-        $node = $config->val( $key, true );
+        $node = $config->val( $key );
         if (
             empty( $node['constructor-parameters'] ) ||
             empty( $node['constructor-parameters'][0]['queue'] )
@@ -52,4 +52,4 @@
         return $config->object( $key );
     }
 
-}
\ No newline at end of file
+}
diff --git a/wikimedia/smash-pig/Core/Listeners/ListenerBase.php 
b/wikimedia/smash-pig/Core/Listeners/ListenerBase.php
index 2c32225..9d35737 100644
--- a/wikimedia/smash-pig/Core/Listeners/ListenerBase.php
+++ b/wikimedia/smash-pig/Core/Listeners/ListenerBase.php
@@ -49,7 +49,7 @@
         */
        protected function validateRemoteIp() {
                // Obtain whitelist
-               $whitelist = $this->c->val( 'security/ip-whitelist', true );
+               $whitelist = $this->c->val( 'security/ip-whitelist' );
 
                // Obtain remote party IP
                $remote_ip = $this->request->getClientIp();
diff --git a/wikimedia/smash-pig/PaymentProviders/Adyen/Audit/AdyenAudit.php 
b/wikimedia/smash-pig/PaymentProviders/Adyen/Audit/AdyenAudit.php
index 3ea8745..e30a79d 100644
--- a/wikimedia/smash-pig/PaymentProviders/Adyen/Audit/AdyenAudit.php
+++ b/wikimedia/smash-pig/PaymentProviders/Adyen/Audit/AdyenAudit.php
@@ -1,6 +1,8 @@
 <?php namespace SmashPig\PaymentProviders\Adyen\Audit;
 
 use OutOfBoundsException;
+use SmashPig\Core\DataFiles\AuditParser;
+use SmashPig\Core\Logging\Logger;
 use SmashPig\Core\NormalizationException;
 use SmashPig\Core\UtcDate;
 use SmashPig\PaymentProviders\Adyen\ReferenceData;
@@ -12,11 +14,41 @@
  * Sends donations, chargebacks, and refunds to queue.
  * 
https://docs.adyen.com/manuals/reporting-manual/settlement-detail-report-structure/settlement-detail-report-journal-types
  */
-class AdyenAudit {
+class AdyenAudit implements AuditParser {
 
-       protected $columnHeaders;
-       protected $ignoredStatuses;
-       protected $fileData = array();
+       protected $columnHeaders = array(
+               'Company Account',
+               'Merchant Account',
+               'Psp Reference',
+               'Merchant Reference',
+               'Payment Method',
+               'Creation Date',
+               'TimeZone',
+               'Type',
+               'Modification Reference',
+               'Gross Currency',
+               'Gross Debit (GC)',
+               'Gross Credit (GC)',
+               'Exchange Rate',
+               'Net Currency',
+               'Net Debit (NC)',
+               'Net Credit (NC)',
+               'Commission (NC)',
+               'Markup (NC)',
+               'Scheme Fees (NC)',
+               'Interchange (NC)',
+               'Payment Method Variant',
+               'Modification Merchant Reference',
+               'Batch Number',
+               'Reserved4',
+               'Reserved5',
+               'Reserved6',
+               'Reserved7',
+               'Reserved8',
+               'Reserved9',
+               'Reserved10',
+       );
+
        protected static $ignoredTypes = array(
                'fee',
                'misccosts',
@@ -38,52 +70,18 @@
                'paidoutreversed',
        );
 
-       public function __construct() {
-               $this->columnHeaders = array(
-                       'Company Account',
-                       'Merchant Account',
-                       'Psp Reference',
-                       'Merchant Reference',
-                       'Payment Method',
-                       'Creation Date',
-                       'TimeZone',
-                       'Type',
-                       'Modification Reference',
-                       'Gross Currency',
-                       'Gross Debit (GC)',
-                       'Gross Credit (GC)',
-                       'Exchange Rate',
-                       'Net Currency',
-                       'Net Debit (NC)',
-                       'Net Credit (NC)',
-                       'Commission (NC)',
-                       'Markup (NC)',
-                       'Scheme Fees (NC)',
-                       'Interchange (NC)',
-                       'Payment Method Variant',
-                       'Modification Merchant Reference',
-                       'Batch Number',
-                       'Reserved4',
-                       'Reserved5',
-                       'Reserved6',
-                       'Reserved7',
-                       'Reserved8',
-                       'Reserved9',
-                       'Reserved10',
-               );
-       }
+       protected $fileData;
 
-       // TODO base class this?
        public function parseFile( $path ) {
-               $this->path = $path;
-               $this->file = fopen( $path, 'r' );
+               $this->fileData = array();
+               $file = fopen( $path, 'r' );
 
                $ignoreLines = 1;
                for ( $i = 0; $i < $ignoreLines; $i++ ) {
-                       fgets( $this->file );
+                       fgets( $file );
                }
 
-               while ( $line = fgetcsv( $this->file, 0, ',', '"', '\\' ) ) {
+               while ( $line = fgetcsv( $file, 0, ',', '"', '\\' ) ) {
                        try {
                                $this->parseLine( $line );
                        } catch ( NormalizationException $ex ) {
@@ -91,7 +89,7 @@
                                Logger::error( $ex->getMessage() );
                        }
                }
-               fclose( $this->file );
+               fclose( $file );
 
                return $this->fileData;
        }
diff --git a/wikimedia/smash-pig/PaymentProviders/Adyen/ReferenceData.php 
b/wikimedia/smash-pig/PaymentProviders/Adyen/ReferenceData.php
index f4c5713..c973f7a 100644
--- a/wikimedia/smash-pig/PaymentProviders/Adyen/ReferenceData.php
+++ b/wikimedia/smash-pig/PaymentProviders/Adyen/ReferenceData.php
@@ -4,7 +4,7 @@
 
 class ReferenceData {
 
-       static $methods = array(
+       protected static $methods = array(
                'alipay' => array(
                        'method' => 'ew',
                        'submethod' => 'ew_alipay',
diff --git a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AuditParser.php 
b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AmazonAudit.php
similarity index 84%
rename from wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AuditParser.php
rename to wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AmazonAudit.php
index d474c89..2d9db5d 100644
--- a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AuditParser.php
+++ b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/AmazonAudit.php
@@ -1,11 +1,12 @@
 <?php namespace SmashPig\PaymentProviders\Amazon\Audit;
 
 use SmashPig\Core\Context;
+use SmashPig\Core\DataFiles\AuditParser;
 
 /**
  * Parses off-Amazon payments reports retrieved from MWS
  */
-class AuditParser {
+class AmazonAudit implements AuditParser {
 
        public function parseFile( $path ) {
                $config = Context::get()->getProviderConfiguration();
diff --git a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/RefundReport.php 
b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/RefundReport.php
index 614eca0..7b745b7 100644
--- a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/RefundReport.php
+++ b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/RefundReport.php
@@ -10,13 +10,14 @@
  * 
http://amazonpayments.s3.amazonaws.com/documents/Sample%20Settlement%20Report.pdf#page=25
  */
 class RefundReport {
-       protected $fileData = array();
+       protected $fileData;
 
        public static function isMine( $filename ) {
                return preg_match( '/.*REFUND_DATA.*csv/', $filename );
        }
 
        public function parse( $path ) {
+               $this->fileData = array();
                $csv = new HeadedCsvReader( $path, ',', 4096, 0 );
 
                while ( $csv->valid() ) {
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/SettlementReport.php 
b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/SettlementReport.php
index c96e28a..7fd3804 100644
--- a/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/SettlementReport.php
+++ b/wikimedia/smash-pig/PaymentProviders/Amazon/Audit/SettlementReport.php
@@ -12,13 +12,14 @@
  */
 class SettlementReport {
 
-       protected $fileData = array();
+       protected $fileData;
 
        public static function isMine( $filename ) {
                return preg_match( '/.*SETTLEMENT_DATA.*csv/', $filename );
        }
 
        public function parse( $path ) {
+               $this->fileData = array();
                // Skip 5 lines at start of file;
                $csv = new HeadedCsvReader( $path, ',', 4096, 5 );
 
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Amazon/Tests/phpunit/AuditTest.php 
b/wikimedia/smash-pig/PaymentProviders/Amazon/Tests/phpunit/AuditTest.php
index 29f13b0..aca8e15 100644
--- a/wikimedia/smash-pig/PaymentProviders/Amazon/Tests/phpunit/AuditTest.php
+++ b/wikimedia/smash-pig/PaymentProviders/Amazon/Tests/phpunit/AuditTest.php
@@ -3,7 +3,7 @@
 
 use SmashPig\Core\Context;
 use SmashPig\Tests\BaseSmashPigUnitTestCase;
-use SmashPig\PaymentProviders\Amazon\Audit\AuditParser;
+use SmashPig\PaymentProviders\Amazon\Audit\AmazonAudit;
 
 /**
  * Verify Amazon audit file processor functions
@@ -20,7 +20,7 @@
         * Normal donation
         */
        public function testProcessDonation() {
-               $processor = new AuditParser();
+               $processor = new AmazonAudit();
                $output = $processor->parseFile( __DIR__ . 
'/../Data/audit/2015-10-01-SETTLEMENT_DATA_371273040777777.csv' );
                $this->assertEquals( 1, count( $output ), 'Should have found 
one donation' );
                $actual = $output[0];
@@ -42,7 +42,7 @@
         * Now try a refund
         */
        public function testProcessRefund() {
-               $processor = new AuditParser();
+               $processor = new AmazonAudit();
                $output = $processor->parseFile( __DIR__ . 
'/../Data/audit/2015-10-06-REFUND_DATA_414749300022222.csv' );
                $this->assertEquals( 1, count( $output ), 'Should have found 
one refund' );
                $actual = $output[0];
@@ -62,7 +62,7 @@
         * And a chargeback
         */
        public function testProcessChargeback() {
-               $processor = new AuditParser();
+               $processor = new AmazonAudit();
                $output = $processor->parseFile( __DIR__ . 
'/../Data/audit/2015-10-06-REFUND_DATA_414749300033333.csv' );
                $this->assertEquals( 1, count( $output ), 'Should have found 
one chargeback' );
                $actual = $output[0];
diff --git 
a/wikimedia/smash-pig/PaymentProviders/AstroPay/Audit/AstroPayAudit.php 
b/wikimedia/smash-pig/PaymentProviders/AstroPay/Audit/AstroPayAudit.php
index dc19101..9ef92f2 100644
--- a/wikimedia/smash-pig/PaymentProviders/AstroPay/Audit/AstroPayAudit.php
+++ b/wikimedia/smash-pig/PaymentProviders/AstroPay/Audit/AstroPayAudit.php
@@ -1,59 +1,55 @@
 <?php namespace SmashPig\PaymentProviders\AstroPay\Audit;
 
 use OutOfBoundsException;
+use SmashPig\Core\DataFiles\AuditParser;
 use SmashPig\Core\Logging\Logger;
 use SmashPig\Core\NormalizationException;
 use SmashPig\Core\UtcDate;
 use SmashPig\PaymentProviders\AstroPay\ReferenceData;
 
-class AstroPayAudit {
+class AstroPayAudit implements AuditParser {
 
-       protected $columnHeaders;
-       protected $ignoredStatuses;
-       protected $fileData = array();
-       protected $file;
+       protected $columnHeaders = array(
+               'Type', // 'Payment' or 'Refund'
+               'Creation date', // YYYY-MM-dd HH:mm:ss
+               'Settlement date', // same format
+               'Reference', // gateway_trxn_id
+               'Invoice', // ct_id.attempt_num
+               'Country',
+               'Payment Method', // corresponds to our payment_submethod
+               'Payment Method Type', // our payment_method
+               'Net Amount (local)',
+               'Amount (USD)', // gross, including fee
+               'currency', // yup, this one is lower case
+               'Status',
+               'User Mail',
+               // These two fields refer to the original donation for refunds
+               'Transaction Reference',
+               'Transaction Invoice',
+               'Fee', // In USD.  AstroPay's processing fee
+               'IOF', // In USD.  Fee for financial transactions in Brazil
+               // The IOF is included in AstroPay's fee, but broken out by 
request
+       );
 
-       public function __construct() {
-               $this->columnHeaders = array(
-                       'Type', // 'Payment' or 'Refund'
-                       'Creation date', // YYYY-MM-dd HH:mm:ss
-                       'Settlement date', // same format
-                       'Reference', // gateway_trxn_id
-                       'Invoice', // ct_id.attempt_num
-                       'Country',
-                       'Payment Method', // corresponds to our 
payment_submethod
-                       'Payment Method Type', // our payment_method
-                       'Net Amount (local)',
-                       'Amount (USD)', // gross, including fee
-                       'currency', // yup, this one is lower case
-                       'Status',
-                       'User Mail',
-                       // These two fields refer to the original donation for 
refunds
-                       'Transaction Reference',
-                       'Transaction Invoice',
-                       'Fee', // In USD.  AstroPay's processing fee
-                       'IOF', // In USD.  Fee for financial transactions in 
Brazil
-                       // The IOF is included in AstroPay's fee, but broken 
out by request
-               );
-               // We don't need do anything with some audit lines
-               $this->ignoredStatuses = array(
-                       'Cancelled', // User pressed cancel or async payment 
expired
-                       'In process', // Chargeback is... charging back? 
'Settled' means done
-                       'Reimbursed', // Chargeback settled in our favor - not 
refunding
-                       'Waiting Details', // Refund is in limbo; we'll wait 
for 'Completed'
-               );
-       }
+       protected $ignoredStatuses = array(
+               'Cancelled', // User pressed cancel or async payment expired
+               'In process', // Chargeback is... charging back? 'Settled' 
means done
+               'Reimbursed', // Chargeback settled in our favor - not refunding
+               'Waiting Details', // Refund is in limbo; we'll wait for 
'Completed'
+       );
+
+       protected $fileData;
 
        public function parseFile( $path ) {
-               $this->path = $path;
-               $this->file = fopen( $path, 'r' );
+               $this->fileData = array();
+               $file = fopen( $path, 'r' );
 
                $ignoreLines = 1;
                for ( $i = 0; $i < $ignoreLines; $i++ ) {
-                       fgets( $this->file );
+                       fgets( $file );
                }
 
-               while ( $line = fgetcsv( $this->file, 0, ';', '"', '\\' ) ) {
+               while ( $line = fgetcsv( $file, 0, ';', '"', '\\' ) ) {
                        try {
                                $this->parseLine( $line );
                        } catch ( NormalizationException $ex ) {
@@ -61,7 +57,7 @@
                                Logger::error( $ex->getMessage() );
                        }
                }
-               fclose( $this->file );
+               fclose( $file );
 
                return $this->fileData;
        }
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Audit/IngenicoAudit.php 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Audit/IngenicoAudit.php
new file mode 100644
index 0000000..2adc816
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/Audit/IngenicoAudit.php
@@ -0,0 +1,199 @@
+<?php namespace SmashPig\PaymentProviders\Ingenico\Audit;
+
+use DOMDocument;
+use DOMElement;
+use RuntimeException;
+use SmashPig\Core\DataFiles\AuditParser;
+use SmashPig\Core\Logging\Logger;
+use SmashPig\Core\UtcDate;
+use SmashPig\PaymentProviders\Ingenico\ReferenceData;
+
+class IngenicoAudit implements AuditParser {
+
+       protected $fileData;
+
+       protected $donationMap = array(
+               'PaymentAmount' => 'gross',
+               'IPAddressCustomer' => 'user_ip',
+               'BillingFirstname' => 'first_name',
+               'BillingSurname' => 'last_name',
+               'BillingStreet' => 'street_address',
+               'BillingCity' => 'city',
+               'ZipCode' => 'postal_code',
+               'BillingCountryCode' => 'country',
+               'BillingEmail' => 'email',
+               'AdditionalReference' => 'contribution_tracking_id',
+               'PaymentProductId' => 'gc_product_id',
+               'OrderID' => 'order_id',
+               'PaymentCurrency' => 'currency',
+               'AmountLocal' => 'gross',
+               'TransactionDateTime' => 'date',
+       );
+
+       protected $refundMap = array(
+               'DebitedAmount' => 'gross',
+               'AdditionalReference' => 'contribution_tracking_id',
+               'OrderID' => 'gateway_parent_id',
+               'DebitedCurrency' => 'gross_currency',
+               'TransactionDateTime' => 'date',
+       );
+
+       protected $recordsWeCanDealWith = array(
+               // Credit card item that has been processed, but not settled.
+               // We take these seriously.
+               // TODO: Why aren't we waiting for +ON?
+               'XON' => 'donation',
+               // Settled "Invoice Payment". Could be invoice, bt, rtbt, check,
+               // prepaid card, ew, cash
+               '+IP' => 'donation',
+               '-CB' => 'chargeback', // Credit card chargeback
+               '-CR' => 'refund', // Credit card refund
+               '+AP' => 'donation', // Direct Debit collected
+       );
+
+       public function parseFile( $path ) {
+               $this->fileData = array();
+               $unzippedFullPath = $this->getUnzippedFile( $path );
+
+               // load the XML into a DOMDocument.
+               // Total Memory Hog Alert. Handle with care.
+               $domDoc = new DOMDocument( '1.0' );
+               Logger::info( "Loading XML from $unzippedFullPath" );
+               $domDoc->load( $unzippedFullPath );
+               unlink( $unzippedFullPath );
+               Logger::info( "Processing" );
+
+               foreach ( $domDoc->getElementsByTagName( 'DataRecord' ) as 
$recordNode ) {
+                       $this->parseRecord( $recordNode );
+               }
+
+               return $this->fileData;
+       }
+
+       protected function parseRecord( DOMElement $recordNode ) {
+               $category = $recordNode->getElementsByTagName( 'Recordcategory' 
)
+                       ->item( 0 )->nodeValue;
+               $type = $recordNode->getElementsByTagName( 'Recordtype' )
+                       ->item( 0 )->nodeValue;
+
+               $compoundType = $category . $type;
+               if ( !array_key_exists( $compoundType, 
$this->recordsWeCanDealWith ) ) {
+                       return;
+               }
+
+               if ( $category === '-' ) {
+                       $refundType = 
$this->recordsWeCanDealWith[$compoundType];
+                       $record = $this->parseRefund( $recordNode, $refundType 
);
+               } else {
+                       $record = $this->parseDonation( $recordNode );
+               }
+               $record = $this->normalizeValues( $record );
+               // TODO: label Connect API donations as 'ingenico'
+               $record['gateway'] = 'globalcollect';
+
+               $this->fileData[] = $record;
+       }
+
+       protected function parseDonation( DOMElement $recordNode ) {
+               $record = $this->xmlToArray( $recordNode, $this->donationMap );
+               $record['gateway_txn_id'] = $record['order_id'];
+               $record = $this->addPaymentMethod( $record );
+               return $record;
+       }
+
+       protected function parseRefund( DOMElement $recordNode, $type ) {
+               $record = $this->xmlToArray( $recordNode, $this->refundMap );
+               $record['type'] = $type;
+               return $record;
+       }
+
+       protected function xmlToArray( DOMElement $recordNode, $map ) {
+               $record = array();
+               foreach ( $map as $theirs => $ours ) {
+                       foreach ( $recordNode->getElementsByTagName( $theirs ) 
as $recordItem ) {
+                               $record[$ours] = $recordItem->nodeValue;  // 
there 'ya go: Normal already.
+                       }
+               }
+               return $record;
+       }
+
+       /**
+        * Adds our normalized payment_method and payment_submethod params based
+        * on the codes that GC uses
+        *
+        * @param array $record The record from the wx file, in array format
+        * @return array The $record param with our normal keys appended
+        */
+       function addPaymentMethod( $record ) {
+               $normalized = ReferenceData::decodePaymentMethod(
+                       $record['gc_product_id']
+               );
+               $record = array_merge( $record, $normalized );
+
+               unset ( $record['gc_product_id'] );
+               return $record;
+       }
+
+       /**
+        * @param string $path Path to original zipped file
+        * @return string Path to unzipped file in working directory
+        */
+       protected function getUnzippedFile( $path ) {
+               $zippedParts = explode( DIRECTORY_SEPARATOR, $path );
+               $zippedFilename = array_pop( $zippedParts );
+               // TODO keep unzipped files around?
+               $workingDirectory = tempnam( sys_get_temp_dir(), 
'ingenico_audit' );
+               if ( file_exists( $workingDirectory ) ) {
+                       unlink( $workingDirectory );
+               }
+               mkdir( $workingDirectory );
+               // whack the .gz on the end
+               $unzippedFilename = substr( $zippedFilename, 0, strlen( 
$zippedFilename ) - 3 );
+
+               $copiedZipPath = $workingDirectory . DIRECTORY_SEPARATOR . 
$zippedFilename;
+               copy( $path, $copiedZipPath );
+               if ( !file_exists( $copiedZipPath ) ) {
+                       throw new RuntimeException(
+                               "FILE PROBLEM: Trying to copy $path to 
$copiedZipPath " .
+                               'for decompression, and something went wrong'
+                       );
+               }
+
+               $unzippedFullPath = $workingDirectory . DIRECTORY_SEPARATOR . 
$unzippedFilename;
+               // decompress
+               Logger::info( "Gunzipping $copiedZipPath" );
+               // FIXME portability
+               $cmd = "gunzip -f $copiedZipPath";
+               exec( escapeshellcmd( $cmd ) );
+
+               // now check to make sure the file you expect actually exists
+               if ( !file_exists( $unzippedFullPath ) ) {
+                       throw new RuntimeException(
+                               'FILE PROBLEM: Something went wrong with 
decompressing WX file: ' .
+                               "$cmd : $unzippedFullPath doesn't exist."
+                       );
+               }
+               return $unzippedFullPath;
+       }
+
+       /**
+        * Normalize amounts, dates, and IDs to match everything else in 
SmashPig
+        * FIXME: do this with transformers migrated in from DonationInterface
+        *
+        * @param array $record
+        * @return array The record, with values normalized
+        */
+       protected function normalizeValues( $record ) {
+               if ( isset( $record['gross'] ) ) {
+                       $record['gross'] = $record['gross'] / 100;
+               }
+               if ( isset( $record['contribution_tracking_id'] ) ) {
+                       $parts = explode( '.', 
$record['contribution_tracking_id'] );
+                       $record['contribution_tracking_id'] = $parts[0];
+               }
+               if ( isset( $record['date'] ) ) {
+                       $record['date'] = UtcDate::getUtcTimestamp( 
$record['date'] );
+               }
+               return $record;
+       }
+}
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/HostedCheckoutProvider.php 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/HostedCheckoutProvider.php
index 6f67e8b..5047135 100644
--- a/wikimedia/smash-pig/PaymentProviders/Ingenico/HostedCheckoutProvider.php
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/HostedCheckoutProvider.php
@@ -28,6 +28,12 @@
        function getHostedPaymentUrl($partialRedirectUrl) {
                return "https://{$this->subdomain}.$partialRedirectUrl";
        }
+
+       function getHostedPaymentStatus($hostedPaymentId){
+               $path = "hostedcheckouts/$hostedPaymentId";
+               $response = $this->api->makeApiCall($path, 'GET');
+               return $response;
+       }
 }
 
 
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/IngenicoPaymentProvider.php 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/IngenicoPaymentProvider.php
index 5d39ba7..e30a10e 100644
--- a/wikimedia/smash-pig/PaymentProviders/Ingenico/IngenicoPaymentProvider.php
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/IngenicoPaymentProvider.php
@@ -18,4 +18,10 @@
                $this->providerConfiguration = 
Context::get()->getProviderConfiguration();
                $this->api = $this->providerConfiguration->object( 'api' );
        }
+
+       public function getPaymentStatus($paymentId){
+               $path = "payments/$paymentId";
+               $response = $this->api->makeApiCall($path, 'GET');
+               return $response;
+       }
 }
diff --git a/wikimedia/smash-pig/PaymentProviders/Ingenico/ReferenceData.php 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/ReferenceData.php
new file mode 100644
index 0000000..b2f69c4
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/ReferenceData.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace SmashPig\PaymentProviders\Ingenico;
+
+use OutOfBoundsException;
+
+class ReferenceData {
+       // FIXME: replace this whole class with payment_(sub)method.yaml files
+
+       protected static $methods = array(
+               '1' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'visa' ),
+               '2' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'amex' ),
+               '3' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'mc' ),
+               '11' => array( 'payment_method' => 'bt', 'payment_submethod' => 
'bt' ),
+               '117' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'maestro' ),
+               '118' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'solo' ),
+               '124' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'laser' ),
+               '125' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'jcb' ),
+               '128' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'discover' ),
+               '130' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'cb' ),
+               '500' => array( 'payment_method' => 'obt', 'payment_submethod' 
=> 'bpay' ),
+               '701' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_nl' ),
+               '702' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_de' ),
+               '703' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_at' ),
+               '704' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_fr' ),
+               '705' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_gb' ),
+               '706' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_be' ),
+               '707' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_ch' ),
+               '708' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_it' ),
+               '709' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_es' ),
+               '805' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_nordea_sweden' ),
+               '809' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_ideal' ),
+               '810' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_enets' ),
+               '836' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_sofortuberweisung' ),
+               '840' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_paypal' ),
+               '841' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_webmoney' ),
+               '843' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_moneybookers' ),
+               '845' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_cashu' ),
+               '849' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_yandex' ),
+               '856' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_eps' ),
+               '861' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_alipay' ),
+               '1503' => array( 'payment_method' => 'cash', 
'payment_submethod' => 'cash_boleto' ),
+       );
+
+       /**
+        * Gets our normalized payment_method and payment_submethod params from 
the
+        * codes that GC uses
+        *
+        * @param string $paymentProductId
+        * @return array containing payment_method and payment_submethod
+        */
+       public static function decodePaymentMethod( $paymentProductId ) {
+               if ( !array_key_exists( $paymentProductId, self::$methods ) ) {
+                       throw new OutOfBoundsException( "Unknown Payment 
Product ID $paymentProductId " );
+               }
+               return self::$methods[$paymentProductId];
+       }
+}
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
new file mode 100644
index 0000000..b38a947
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
Binary files differ
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
new file mode 100644
index 0000000..ebb1109
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
Binary files differ
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/hostedPaymentStatus.response
 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/hostedPaymentStatus.response
new file mode 100644
index 0000000..9d35135
--- /dev/null
+++ 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/hostedPaymentStatus.response
@@ -0,0 +1,51 @@
+HTTP/1.1 200 OK
+Date: Mon, 30 Jan 2017 17:58:02 GMT
+Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1t
+X-Powered-By: Servlet/3.0 JSP/2.2
+Transfer-Encoding: chunked
+Content-Type: application/json
+
+{
+  "createdPaymentOutput": {
+    "payment": {
+      "id": "000000891566072501680000200001",
+      "paymentOutput": {
+        "amountOfMoney": {
+          "amount": 2345,
+          "currencyCode": "USD"
+        },
+        "references": {
+          "paymentReference": "0"
+        },
+        "paymentMethod": "card",
+        "cardPaymentMethodSpecificOutput": {
+          "paymentProductId": 1,
+          "authorisationCode": "123456",
+          "card": {
+            "cardNumber": "************7977",
+            "expiryDate": "1220"
+          },
+          "fraudResults": {
+            "avsResult": "0",
+            "cvvResult": "M",
+            "fraudServiceResult": "no-advice"
+          }
+        }
+      },
+      "status": "PENDING_APPROVAL",
+      "statusOutput": {
+        "isCancellable": true,
+        "statusCode": 600,
+        "statusCodeChangeDateTime": "20140717145840",
+        "isAuthorized": true
+      }
+    },
+    "paymentCreationReferences": {
+      "additionalReference": "00000089156607250168",
+      "externalReference": "000000891566072501680000200001"
+    },
+    "tokens": ""
+  },
+  "status": "PAYMENT_CREATED"
+}
+
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/paymentStatus.response
 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/paymentStatus.response
new file mode 100644
index 0000000..216a1cb
--- /dev/null
+++ 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/paymentStatus.response
@@ -0,0 +1,43 @@
+HTTP/1.1 200 OK
+Date: Mon, 30 Jan 2017 17:58:02 GMT
+Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1t
+X-Powered-By: Servlet/3.0 JSP/2.2
+Transfer-Encoding: chunked
+Content-Type: application/json
+
+{
+  "id": "000000850010000188180000200001",
+  "paymentOutput":
+  {
+    "amountOfMoney":
+    {
+      "amount": 100,
+      "currencyCode": "EUR"
+    },
+    "references": {
+      "merchantReference": "AcmeOrder0001",
+      "paymentReference": "0"
+    },
+    "paymentMethod": "card",
+    "cardPaymentMethodSpecificOutput": {
+      "paymentProductId": 1,
+      "authorisationCode": "726747",
+      "card": {
+        "cardNumber": "************7977",
+        "expiryDate": "1220"
+      },
+      "fraudResults": {
+        "avsResult": "0",
+        "cvvResult": "0",
+        "fraudServiceResult": "no-advice"
+      }
+    }
+  },
+  "status": "PENDING_APPROVAL",
+  "statusOutput": {
+    "isCancellable": true,
+    "statusCode": 600,
+    "statusCodeChangeDateTime": "20140630154921",
+    "isAuthorized": true
+  }
+}
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
new file mode 100644
index 0000000..8fc709c
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
Binary files differ
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
new file mode 100644
index 0000000..3fe756c
--- /dev/null
+++ b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace SmashPig\PaymentProviders\Ingenico\Tests;
+
+use SmashPig\PaymentProviders\Ingenico\Audit\IngenicoAudit;
+use SmashPig\Tests\BaseSmashPigUnitTestCase;
+
+/**
+ * @group Audit
+ * @group Ingenico
+ */
+class AuditTest extends BaseSmashPigUnitTestCase {
+       /**
+        * Normal donation
+        */
+       public function testProcessDonation() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/donation.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one donation' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'gross' => 3.00,
+                       'contribution_tracking_id' => '5551212',
+                       'currency' => 'USD',
+                       'order_id' => '987654321',
+                       'gateway_txn_id' => '987654321',
+                       'payment_method' => 'cc',
+                       'payment_submethod' => 'visa',
+                       'date' => 1501368968,
+                       'user_ip' => '111.222.33.44',
+                       'first_name' => 'Arthur',
+                       'last_name' => 'Aardvark',
+                       'street_address' => '1111 Fake St',
+                       'city' => 'Denver',
+                       'country' => 'US',
+                       'email' => 'dutch...@flying.net',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse 
donation correctly' );
+       }
+
+       /**
+        * Now try a refund
+        */
+       public function testProcessRefund() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/refund.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one refund' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'contribution_tracking_id' => '5551212',
+                       'date' => 1500942220,
+                       'gross' => 100,
+                       'gateway_parent_id' => '123456789',
+                       'gross_currency' => 'USD',
+                       'type' => 'refund',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse refund 
correctly' );
+       }
+
+       /**
+        * And a chargeback
+        */
+       public function testProcessChargeback() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/chargeback.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one chargeback' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'contribution_tracking_id' => '5551212',
+                       'date' => 1495023569,
+                       'gross' => 200,
+                       'gateway_parent_id' => '5167046621',
+                       'gross_currency' => 'USD',
+                       'type' => 'chargeback',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse 
chargeback correctly' );
+       }
+}
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/HostedCheckoutProviderTest.php
 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/HostedCheckoutProviderTest.php
index b504893..96c52bb 100644
--- 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/HostedCheckoutProviderTest.php
+++ 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/HostedCheckoutProviderTest.php
@@ -2,6 +2,7 @@
 
 namespace SmashPig\PaymentProviders\Ingenico\Tests;
 
+use PHPUnit_Framework_MockObject_MockObject;
 use SmashPig\PaymentProviders\Ingenico\HostedCheckoutProvider;
 use SmashPig\Tests\BaseSmashPigUnitTestCase;
 
@@ -61,4 +62,17 @@
                $expectedUrl = 'https://payments.test.' . $partialRedirectUrl;
                $this->assertEquals($expectedUrl, $hostedPaymentUrl);
        }
+
+       public function testGetHostedPaymentStatus(){
+               $hostedPaymentId = '8915-28e5b79c889641c8ba770f1ba576c1fe';
+               $this->setUpResponse(__DIR__ . 
"/../Data/hostedPaymentStatus.response", 200);
+               $this->curlWrapper->expects( $this->once() )
+                       ->method( 'execute' )->with(
+                               
$this->equalTo("https://api-sandbox.globalcollect.com/v1/1234/hostedcheckouts/$hostedPaymentId";),
+                               $this->equalTo('GET')
+                       );
+               $response = 
$this->provider->getHostedPaymentStatus($hostedPaymentId);
+               $this->assertEquals('PAYMENT_CREATED', $response['status']);
+
+       }
 }
diff --git 
a/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/IngenicoPaymentProviderTest.php
 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/IngenicoPaymentProviderTest.php
new file mode 100644
index 0000000..eaa54a6
--- /dev/null
+++ 
b/wikimedia/smash-pig/PaymentProviders/Ingenico/Tests/phpunit/IngenicoPaymentProviderTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace SmashPig\PaymentProviders\Ingenico\Tests;
+
+use PHPUnit_Framework_MockObject_MockObject;
+use SmashPig\PaymentProviders\Ingenico\IngenicoPaymentProvider;
+use SmashPig\Tests\BaseSmashPigUnitTestCase;
+
+/**
+ * @group Ingenico
+ */
+class IngenicoPaymentProviderTest extends BaseSmashPigUnitTestCase {
+       /**
+        * @var PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $provider;
+
+       public function setUp() {
+               parent::setUp();
+               $this->setProviderConfiguration( 'ingenico' );
+               $this->provider = 
$this->getMockForAbstractClass('\SmashPig\PaymentProviders\Ingenico\IngenicoPaymentProvider');
+       }
+
+       public function testGetPaymentStatus(){
+               $paymentId = '000000850010000188180000200001';
+               $this->setUpResponse(__DIR__ . 
'/../Data/paymentStatus.response', 200);
+               $this->curlWrapper->expects( $this->once() )
+                       ->method( 'execute' )->with(
+                               
$this->equalTo("https://api-sandbox.globalcollect.com/v1/1234/payments/$paymentId";),
+                               $this->equalTo('GET')
+                       );
+               $response = $this->provider->getPaymentStatus($paymentId);
+               $this->assertEquals($paymentId, $response['id']);
+       }
+}
\ No newline at end of file
diff --git 
a/wikimedia/smash-pig/PaymentProviders/PayPal/Tests/Data/recurring_payment_profile_created_transformed.json
 
b/wikimedia/smash-pig/PaymentProviders/PayPal/Tests/Data/recurring_payment_profile_created_transformed.json
index ce73c80..0c7ad8e 100644
--- 
a/wikimedia/smash-pig/PaymentProviders/PayPal/Tests/Data/recurring_payment_profile_created_transformed.json
+++ 
b/wikimedia/smash-pig/PaymentProviders/PayPal/Tests/Data/recurring_payment_profile_created_transformed.json
@@ -15,5 +15,7 @@
   "start_date": 1492533928,
   "date": 1492533928,
   "gateway": "paypal_ec",
-  "recurring": "1"
+  "recurring": "1",
+  "gross": "140",
+  "currency": "JPY"
 }
diff --git a/wikimedia/smash-pig/config/paypal/main.yaml 
b/wikimedia/smash-pig/config/paypal/main.yaml
index cbd7aaf..8ee159f 100644
--- a/wikimedia/smash-pig/config/paypal/main.yaml
+++ b/wikimedia/smash-pig/config/paypal/main.yaml
@@ -28,6 +28,7 @@
     # FIXME This is only true for refund messages.  Where to represent?
     #txn_id: gateway_refund_id
     mc_currency: currency
+    currency_code: currency
     # FIXME rename refund_type
     reason_code: type
     #test_ipn:  # signals test mode
@@ -46,6 +47,9 @@
     # FIXME this too
     address_name: supplemental_address_1
     gateway: gateway
+    # NOTE: order matters. When multiple PayPal fields map to the
+    # same SmashPig field, PayPal fields listed later take precedence.
+    amount_per_cycle: gross
     mc_gross: gross
     mc_amount3: gross
     amount3: gross

-- 
To view, visit https://gerrit.wikimedia.org/r/370091
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If480186992d97fc1ef5748e919a24c0f16d4e662
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/fundraising/crm/vendor
Gerrit-Branch: master
Gerrit-Owner: Ejegg <ej...@ejegg.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to